Shiro源码解析之认证代码走查
SpringBoot集成Shiro的Demo比较好找,我这篇文章就默认大家已经有相应的基础了,如果还没有集成过,那可能还是先去了解一下比较好,所以,这里就不详细介绍集成的相关内容了。
1.实现AuthorizingRealm
在Shiro集成过程中,有一个重要的步骤就是自定义一个Realm类(如下代码块的类UserRealm),这个类应该继承类AuthorizingRealm,并实现方法doGetAuthenticationInfo、doGetAuthorizationInfo,将认证信息、授权信息分别放入类SimpleAuthenticationInfo、SimpleAuthorizationInfo的实体中。
public class UserRealm extends AuthorizingRealm
{
private static final Logger log = LoggerFactory.getLogger(UserRealm.class);
@Autowired
private IMenuService menuService;
@Autowired
private IRoleService roleService;
@Autowired
private LoginService loginService;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0)
{
User user = ShiroUtils.getUser();
// 角色列表
Set<String> roles = new HashSet<String>();
// 功能列表
Set<String> menus = new HashSet<String>();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 管理员拥有所有权限
if (user.isAdmin()) {
info.addRole("admin");
//*:*:* 资源:操作:对象,如order:create:1
info.addStringPermission("*:*:*");
} else {
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 角色加入AuthorizationInfo认证对象
info.setRoles(roles);
// 权限加入AuthorizationInfo认证对象
info.setStringPermissions(menus);
}
return info;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
//替换为自己封装的token
CustomToken upToken = (CustomToken) token;
String username = upToken.getUsername();
LoginType type = upToken.getType();
String role = upToken.getRole();
String password = "";
if (upToken.getPassword() != null){
password = new String(upToken.getPassword());
}
User user = null;
try {
user = loginService.login(username, password,type,role);
} catch (CaptchaException e) {
throw new AuthenticationException(e.getMessage(), e);
} catch (UserNotExistsException e) {
throw new UnknownAccountException(e.getMessage(), e);
} catch (UserPasswordNotMatchException e) {
throw new IncorrectCredentialsException(e.getMessage(), e);
} catch (UserPasswordRetryLimitExceedException e) {
throw new ExcessiveAttemptsException(e.getMessage(), e);
} catch (UserBlockedException e) {
throw new LockedAccountException(e.getMessage(), e);
} catch (RoleBlockedException e) {
throw new LockedAccountException(e.getMessage(), e);
} catch (Exception e) {
log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
throw new AuthenticationException(e.getMessage(), e);
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
}
2. 触发doGetAuthenticationInfo认证
在根据我们自己的业务情况,实现了doGetAuthenticationInfo方法后,我们就可以通过调用SecurityUtils.getSubject().login()方法来触发调用doGetAuthenticationInfo方法进行认证了。
Subject subject = SecurityUtils.getSubject();
subject.login(token);
那么,从执行subject.login方法到调用到我们自定义的doGetAuthenticationInfo方法,到底Shiro框架是怎么实现的呢?以下就是把我的代码追踪过程整理如下:
- 首先调用Subject的实现类DelegatingSubject里的login方法,这个可以注意一下这个参数值token,其实就是我们在web项目中登录时所输入的用户名、密码等信息,但是需要实现AuthenticationToken接口。
public class DelegatingSubject implements Subject {
protected transient SecurityManager securityManager;
public void login(AuthenticationToken token) throws AuthenticationException {
//省略...
Subject subject = securityManager.login(this, token);
//省略...
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
//we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
//下面这句话非常重要,登录后,principals才会有身份信息;若没有执行登录操作,直接调用shiro的权限校验,如注解@RequiresPermissions,会先判断该字段是否有值,若没有则会提示需要先登录
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
//省略...
}
}
- 然后进入到securityManager.login(this, token),默认跳转到实现类DefaultSecurityManager中的login方法(关于SecurityManager的相关实现类图,我会附在文档后面)。
public class DefaultSecurityManager extends SessionsSecurityManager {
//省略...
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
}
//省略...
}
}
- DefaultSecurityManager的login方法中继续调用父类AuthenticatingSecurityManager的authenticate方法。
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
private Authenticator authenticator;
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
}
- 类AuthenticatingSecurityManager的authenticate方法中调用Authenticator类的authenticate方法,找到其实现类AbstractAuthenticator里的authenticate方法(注意,这个方法有final修饰符,不可被重写)。另外,authenticate方法里调用抽象方法doAuthenticate,所以接下来继续找该方法的实现。
public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
//省略...
AuthenticationInfo info;
try {
info = doAuthenticate(token);
}
//省略...
}
protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
throws AuthenticationException;
}
- ModularRealmAuthenticator类对doAuthenticate进行了实现,方法体里的getRealms()里获取到的Realm就是我们之前定义的UserRealm。在只定义了一个Realm的情况下,会调用方法doSingleRealmAuthentication,进而调用到realm的getAuthenticationInfo方法。
public class ModularRealmAuthenticator extends AbstractAuthenticator {
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
//省略...
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
//省略...
AuthenticationInfo info = realm.getAuthenticationInfo(token);
//省略...
return info;
}
}
- AuthenticatingRealm类对getAuthenticationInfo方法进行实现,并调用doGetAuthenticationInfo方法,这是一个抽象方法,所以会调用它子类的实现。
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
//省略...
info = doGetAuthenticationInfo(token);
//省略...
return info;
}
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}
代码走查到这一步,再往下查找,就会调用到我们的实现类UserRealm里对doGetAuthenticationInfo方法的具体实现了(这个还有印象吧,就是第一个代码块的内容)。
走完一次源码的过程,就会觉得比较通畅,消除神秘感了,不喜欢那种黑盒子的感觉。接下来,就是继续把授权的调用链过程发出来。至于这里面所使用到的设计思想,可以有空再仔细研究。
附图