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],并且在注册时或修改密码时进行了加密处理)进行比较,得出认证结果。