shiro——Realm

Realm翻译过来是“域”,对应一个文本文件,一个数据库,总之就是资源。

Realm类结构

 

父接口Realm只有三个方法

public interface Realm {
    String getName();

    boolean supports(AuthenticationToken var1);

    AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
}

 shiro完成认证是通过调用第三个方法完成的。

接下来看看Realm常用的几个实现类

一、IniRealm

1)成员变量:

public static final String USERS_SECTION_NAME = "users";
public static final String ROLES_SECTION_NAME = "roles";
private static final transient Logger log = LoggerFactory.getLogger(IniRealm.class);
private String resourcePath;
private Ini ini;

     看到前面两个静态字符串常量就是Ini配置文件中的[users]段和[roles]段,resourcePath存储ini文件的路径,Ini对象用于解析ini文件,并存储解析后的数据(键值对的形式)。

2)构造函数:

public IniRealm() {
    }

    public IniRealm(Ini ini) {
        this();
        this.processDefinitions(ini);
    }

    public IniRealm(String resourcePath) {
        this();
        Ini ini = Ini.fromResourcePath(resourcePath);
        this.ini = ini;
        this.resourcePath = resourcePath;
        this.processDefinitions(ini);
    }

 我们常用第三个构造函数,即传递ini配置文件的路径。在第三个构造函数中调用了Ini的fromResourcePath方法,并且返回一个Ini对象,我们来看看Ini这个类.....

public class Ini implements Map<String, Ini.Section> {
    private static final transient Logger log = LoggerFactory.getLogger(Ini.class);
    public static final String DEFAULT_SECTION_NAME = "";
    public static final String DEFAULT_CHARSET_NAME = "UTF-8";
    public static final String COMMENT_POUND = "#";
    public static final String COMMENT_SEMICOLON = ";";
    public static final String SECTION_PREFIX = "[";
    public static final String SECTION_SUFFIX = "]";
    protected static final char ESCAPE_TOKEN = '\\';
    private final Map<String, Ini.Section> sections;

    public Ini() {
        this.sections = new LinkedHashMap();
    }

    public Ini(Ini defaults) {
        this();
        if (defaults == null) {
            throw new NullPointerException("Defaults cannot be null.");
        } else {
            Iterator var2 = defaults.getSections().iterator();

            while(var2.hasNext()) {
                Ini.Section section = (Ini.Section)var2.next();
                Ini.Section copy = new Ini.Section(section);
                this.sections.put(section.getName(), copy);
            }

        }
    }

以上只是Ini类的部分代码,从成员变量你就能明白,这个类做的工作,那就是解析ini配置文件,并且是保存在sections中,观察这个sections的类型是Map类型,后面一个泛型是Ini.Section类型,这个Section是Ini的内部类。部分代码如下:

public static class Section implements Map<String, String> {
        private final String name;
        private final Map<String, String> props;

        private Section(String name) {
            if (name == null) {
                throw new NullPointerException("name");
            } else {
                this.name = name;
                this.props = new LinkedHashMap();
            }
        }

成员变量是map类型,Ini配置文件解析过程中,会存储一些键值对。

二、PropetiesRealm

从类名可以看出应该是解析properties文件的,部分代码如下:

public class PropertiesRealm extends TextConfigurationRealm implements Destroyable, Runnable {
    private static final int DEFAULT_RELOAD_INTERVAL_SECONDS = 10;
    private static final String USERNAME_PREFIX = "user.";
    private static final String ROLENAME_PREFIX = "role.";
    private static final String DEFAULT_RESOURCE_PATH = "classpath:shiro-users.properties";
    private static final Logger log = LoggerFactory.getLogger(PropertiesRealm.class);
    protected ExecutorService scheduler = null;
    protected boolean useXmlFormat = false;
    protected String resourcePath = "classpath:shiro-users.properties";
    protected long fileLastModified;
    protected int reloadIntervalSeconds = 10;

    public PropertiesRealm() {
    }

注意他的成员变量,UserName_Prefix、RoleName_Prefix,这两个静态变量限定了你的properties文件中键值对的前缀名必须符合规则,另外里面还有一个默认的properties文件名及路径,当然也提供了setResourcePath(String path)方法来使用自己的properties文件,还有一个需要注意的是该类实现了Runnable接口,里面存在着一些重新加载properties文件的方法,可能可以实现开启另一个线程向配置文件中添加新的内容,实现一些特殊的业务逻辑。

三JdbcRealm

与数据库关联的域,即认证方式是通过与数据库的数据作比较。部分代码如下:

public class JdbcRealm extends AuthorizingRealm {
    protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
    protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";
    protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
    protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
    private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
    protected DataSource dataSource;
    protected String authenticationQuery = "select password from users where username = ?";
    protected String userRolesQuery = "select role_name from user_roles where username = ?";
    protected String permissionsQuery = "select permission from roles_permissions where role_name = ?";
    protected boolean permissionsLookupEnabled = false;
    protected JdbcRealm.SaltStyle saltStyle;

    public JdbcRealm() {
        this.saltStyle = JdbcRealm.SaltStyle.NO_SALT;
    }

默认写好了一些数据库语句,默认有三个表:users、user_roles、roles_permissions,还有默认的字段名,这些都可以从sql语句中看出来。如果你一开始使用JdbcRealm练习时,往往使用他的默认sql语句,所以建表时需要注意名字一样。当然你可以增加新的表字段,但是基本的还是要有的。

里面还有一个JdbcRealm.SaltStyle枚举类型的成员变量,该变量与密码加密有关。

public static enum SaltStyle {
        NO_SALT,
        CRYPT,
        COLUMN,
        EXTERNAL;

        private SaltStyle() {
        }
    }

根据你的选择,会进行不同的处理,比如不加密、加密等等,默认是NO_SALT的。

这里面起认证作用的方法是:

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken)token;
        String username = upToken.getUsername();
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        } else {
            Connection conn = null;
            SimpleAuthenticationInfo info = null;

            try {
                String salt;
                try {
                    conn = this.dataSource.getConnection();
                    String password = null;
                    salt = null;
                    switch(this.saltStyle) {
                    case NO_SALT:
                        password = this.getPasswordForUser(conn, username)[0];
                        break;
                    case CRYPT:
                        throw new ConfigurationException("Not implemented yet");
                    case COLUMN:
                        String[] queryResults = this.getPasswordForUser(conn, username);
                        password = queryResults[0];
                        salt = queryResults[1];
                        break;
                    case EXTERNAL:
                        password = this.getPasswordForUser(conn, username)[0];
                        salt = this.getSaltForUser(username);
                    }

                    if (password == null) {
                        throw new UnknownAccountException("No account found for user [" + username + "]");
                    }

                    info = new SimpleAuthenticationInfo(username, password.toCharArray(), this.getName());
                    if (salt != null) {
                        info.setCredentialsSalt(Util.bytes(salt));
                    }
                } catch (SQLException var12) {
                    salt = "There was a SQL error while authenticating user [" + username + "]";
                    if (log.isErrorEnabled()) {
                        log.error(salt, var12);
                    }

                    throw new AuthenticationException(salt, var12);
                }
            } finally {
                JdbcUtils.closeConnection(conn);
            }

            return info;
        }
    }

这个方法存在另外一个重载的方法,但一般这个用的比较多。然后真正执行sql语句的是getPasswordForUser、getRoleNamesForUser、getPermissions、getSaltForUser。就拿getPasswordForUser来说:

private String[] getPasswordForUser(Connection conn, String username) throws SQLException {
        boolean returningSeparatedSalt = false;
        String[] result;
        switch(this.saltStyle) {
        case NO_SALT:
        case CRYPT:
        case EXTERNAL:
            result = new String[1];
            break;
        case COLUMN:
        default:
            result = new String[2];
            returningSeparatedSalt = true;
        }

        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            ps = conn.prepareStatement(this.authenticationQuery);
            ps.setString(1, username);
            rs = ps.executeQuery();

            for(boolean foundResult = false; rs.next(); foundResult = true) {
                if (foundResult) {
                    throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
                }

                result[0] = rs.getString(1);
                if (returningSeparatedSalt) {
                    result[1] = rs.getString(2);
                }
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }

        return result;
    }

简单说一下吧,比如你的saltStyle的枚举类型为JdbcRealm.SaltStyle.Column,那就表示在users这张表中salt以独立的一列存在,所以返回结果时,需要容量为2的String数组储存,前端拿到的密码需要与这个salt进行一定的运算(加密算法)得到一个加密后的密码串,再于数据库中查询出来的密码(存放在String[0],并且在注册时或修改密码时进行了加密处理)进行比较,得出认证结果。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值