Realm 在 Shiro 中起着非常重要的作用,主要用来进行认证和授权(设置登录用户的角色和权限等信息),下面就是我对项目中 Realm 的认证过程的一些理解。
项目在集成 Shiro 框架时需要实现一个自定义的 Realm ,通常是继承 AuthorizingRealm ,并覆写两个方法:doGetAuthenticationInfo 和 doGetAuthorizationInfo。 下面是示例代码展现:
public class MyShiroRealm extends AuthorizingRealm {
/**
* 认证 登录
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
final String userName = userToken.getUsername(); // 获取要登录的用户名
User user = userService.findByUserName(userName); // 调用服务查询用户
final String password = user.getPassword(); // 拿到该用户的密码
// 构造 SimpleAuthenticationInfo 并返回
return new SimpleAuthenticationInfo(userName, password, this.getClass().getName());
}
/**
* 授权 角色 权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 此处省略关于角色、权限相关的代码
}
}
以上代码为示例代码,仅用于举例,在实际项目中需要有完善的判断逻辑。
那么 Shiro 是如何调用自定义的 Realm 来进行认证的呢?
通过阅读源码可以看到如下的调用关系:
SecurityUtils.getSubject().login(AuthenticationToken)
--> SecurityManager.login()
--> AuthenticatingSecurityManager.authenticate()
--> Authenticator.authenticate()
--> Authenticator.doAuthenticate()
--> Realm.getAuthenticationInfo()
--> MyShiroRealm.doGetAuthenticationInfo()
经过一系列的调用,最终到自定义的 doGetAuthenticationInfo() 方法
上面所说的这一列调用说明了程序最终会调用到我们自定义的Realm,那么下面继续说明是在哪一步进行密码比较来完成认证的:
在倒数第二步的 Realm.getAuthenticationInfo() 源码中(省略了部分非核心代码)可以看到:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 下面是获取 AuthenticationInfo ,就是从自定义的 MyShiroRealm 的 doGetAuthenticationInfo(token) 方法获得的
// 如果使用了缓存,就先从缓存中获得
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
info = doGetAuthenticationInfo(token);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info); // 放入缓存中
}
}
if (info != null) {
// 在这里调用匹配器进行密码比较
// 匹配器可自定义
assertCredentialsMatch(token, info);
}
return info;
}
对于 assertCredentialsMatch() 方法的实现如下:
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
// 通过配置可以在这里获取我们自定义的匹配器
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) { // 在这里进行比较
// 抛出匹配不成功的异常
}
} else {
// 抛出没有配置匹配器异常
}
}
假如我们使用 SimpleCredentialsMatcher 这个 CredentialsMatcher 实现类:
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);
}
可以看到 SimpleCredentialsMatcher 会去拿在Realm中获取的 AuthenticationInfo 中赋值的密码和在进行登录操作时传入的 AuthenticationToken 中设置的密码进行比较。
理解了这个基本过程,我们就可以通过自定义的配置来实现我们想要的认证方式了。