突然接到了一个需求,一个系统需要实现多种认证登录。
之前都是修改shiro的realm,根据不同情况去判断,会造成代码特别臃肿。
所以这次尝试用多个realm 来实现多种认证方式登录。
认证之前先了解了一下shiro多realm处理的三种策略:
SecurityManager 接口继承了 Authenticator,另外还有一个 ModularRealmAuthenticator 实现,其委托给多个 Realm 进行验证,验证规则通过 AuthenticationStrategy 接口指定,默认提供的实现:
- FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
- AtLeastOneSuccessfulStrategy:只要有一个 Realm 验证成功即可,和 FirstSuccessfulStrategy不同,返回所有 Realm 身份验证成功的认证信息;
- AllSuccessfulStrategy:所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator 默认使用 AtLeastOneSuccessfulStrategy 策略。
首先配置多个realm认证和认证的处理策略,这里采用默认处理策略:
<!-- 定义 Shiro 主要业务对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 多Realm配置,和处理策略 -->
<property name="authenticator">
<bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy">
<!-- 至少有一个Realm认证通过 -->
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/>
</property>
</bean>
</property>
<property name="realms">
<list>
<ref bean="systemRealm"/>
<ref bean="testLoginRealm"/>
<ref bean="keyLoginRealm"/>
</list>
</property>
<property name="subjectFactory" ref="casSubjectFactory"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="cacheManagerShiro"/>
</bean>
多realm认证调用流程,采用默认AtLeastOneSuccessfulStrategy 策略
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
//在所有Reaml之前使用
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
//在每个Reaml前使用
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, t);
}
}
//在每个Reaml后使用
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
//在所有Reaml之后使用
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
可以通过自己继承AbstractAuthenticationStrategy抽象类来实现:beforeAllAttempts、beforeAttempt、afterAttempt、afterAllAttempts来实现自定义策略。
笔记:
当多个realm认证成功,AtLeastOneSuccessfulStrategy策略会返回多个principal,
当调用subject.getPrincipal(),实际会获取principals中的第一个。
如果系统中不处理多个principal 情况,采用FirstSuccessfulStrategy和AtLeastOneSuccessfulStrategy策略登录结果相同
//调用代码
Subject subject = getSubject(request, response);
Session session = subject.getSession();
subject.getPrincipal()
//实际会获取principals中的第一个
//org.apache.shiro.subject.support.DelegatingSubject.java
public Object getPrincipal() {
return getPrimaryPrincipal(getPrincipals());
}
private Object getPrimaryPrincipal(PrincipalCollection principals) {
if (!CollectionUtils.isEmpty(principals)) {
return principals.getPrimaryPrincipal();
}
return null;
}
//org.apache.shiro.subject.SimplePrincipalCollection.java
public Object getPrimaryPrincipal() {
if (isEmpty()) {
return null;
}
return iterator().next();
}