实现
其实在前面【登录功能实现】这节查看源码的过程中我们有看到这个方法:
// org.apache.shiro.authc.pam.ModularRealmAuthenticator#doAuthenticate
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
// <1>
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
在 <1>
处实际上是从当前 ModularRealmAuthenticator
实例中获取一个 realms
属性,而这个属性是一个集合,该属性是用来存放我们所注册的 Realm
实例。
显然,Realm
实例是可以有多个的,我们只需要将它们注入给 ModularRealmAuthenticator
实例,然后将 ModularRealmAuthenticator
实例交给安全管理器即可。如下:
1、创建第二个 Realm
类,密码使用 SHA1 加密:
// com.zze.shiro.realms.SecondRealm
package com.zze.shiro.realms;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
public class SecondRealm extends AuthenticatingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// authenticationToken 实际就是 Controller 中通过 Subject.Login 方法传入的 token,保存了前台输入的密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
// 获取页面传入的用户名
String username = usernamePasswordToken.getUsername();
// 根据用户名从数据库获取密码,假如获取到的是 123456 加密后的密码
Object credentials = "123456"; // 要加密的对象
String hashAlgorithmName = "SHA1"; // 加密方式
ByteSource salt = ByteSource.Util.bytes(username); // 以用户名当做盐值
int hashIterations = 1024; // 加密次数,与 Spring 中配置一致
credentials = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
// 假如 unknown 用户名不存在
if ("unknown".equals(username)) {
throw new UnknownAccountException("用户名不存在");
}
// 假如 monster 用户名已经被锁定
if ("monster".equals(username)) {
throw new LockedAccountException("用户已被锁定");
}
// 根据用户情况,构建 AuthenticationInfo 对象返回,通常使用的实现类为 SimpleAuthenticationInfo
// a. principal : 认证的实体信息,可以是 username,也可以是用户对应的实体对象
Object principal = username;
// b. credentials : 密码
// c. realmName : 当前 realm 对象的 name,调用父类的 getName() 方法获得
String realmName = getName();
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials, salt, realmName);
return simpleAuthenticationInfo; // 返回认证信息,交给 Shiro 完成比对
}
}
2、在 Spring 核心配置文件中配置多 Realm
:
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--
1、配置安全管理器 SecurityManager
-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="realm" ref="jdbcRealm"/>
<property name="authenticator" ref="authenticator"/>
</bean>
<!--
2、配置缓存管理器 CacheManager
a、需加入 ehcache jar 和 配置文件
-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!--
4、配置 lifecycleBeanPostProcessor,可以自动调用配置在 Spring IOC 容器中 Shiro bean 的生命周期方法
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--
5、启用 IOC 容器中使用 Shiro 注解,必须在配置 lifecycleBeanPostProcessor 之后才可使用
-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/list.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<property name="filterChainDefinitions">
<value>
/logout = anon
/login* = anon
/** = authc
</value>
</property>
</bean>
<bean id="jdbcRealm" class="com.zze.shiro.realms.LoginRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密次数-->
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>
<bean id="secondRealm" class="com.zze.shiro.realms.SecondRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式-->
<property name="hashAlgorithmName" value="SHA1"/>
<!--加密次数-->
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>
<!--配置多 Realm-->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="jdbcRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
</bean>
</beans>
此时,我们再登录就会使用多 Realm 认证。
认证策略
上面我们已经实现了多 Realm
认证,但肯定还会有一个疑问。我们现在使用的是多个 Realm
认证,那如何才算认证成功呢?对于这个问题的解决,Shiro 为我们提供了认证策略,要使用认证策略我们需要了解下面几个类,这几个类都实现了 org.apache.shiro.authc.pam.AuthenticationStrategy
接口:
org.apache.shiro.authc.pam.FirstSuccessfulStrategy
:只要有一个Realm
验证成功即可,只返回第一个Realm
身份验证成功的认证信息,其它的忽略。org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
:只要有一个Realm
验证成功即可,和FirstSuccessfulStrategy
不同,将返回所有Realm
身份验证成功的认证信息。org.apache.shiro.authc.pam.AllSuccessfulStrategy
:所有Realm
验证成功才算成功,且返回所有Realm
身份验证成功的认证信息,如果有一个失败就失败了。
org.apache.shiro.authc.pam.ModularRealmAuthenticator
默认使用的是 org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
策略。
那我们如何配置认证策略呢,还是从源码看。使用多 Realm
认证时会经过这个方法:
// org.apache.shiro.authc.pam.ModularRealmAuthenticator#doMultiRealmAuthentication
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
// <1>
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
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.isWarnEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.warn(msg, t);
}
}
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
可以看到在 <1>
处 getAuthenticationStrategy()
就是在获取认证策略,而认证策略也是 ModularRealmAuthenticator
的一个属性,所以我们只需要将我们要使用的认证策略类注入给认证器 ModularRealmAuthenticator
实例即可:
<!--认证器配置多 Realm-->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!--配置多 Realm-->
<property name="realms">
<list>
<ref bean="jdbcRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
<!--配置认证策略-->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
</property>
</bean>
在上面我们是把 realms
属性配置给 ModularRealmAuthenticator
,但我们通常是直接把 realms
直接配置给安全管理器,如下:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="authenticator" ref="authenticator"/>
<!--配置多 Realm-->
<property name="realms">
<list>
<ref bean="jdbcRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
</bean>
<!--认证器配置多 Realm-->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!--配置认证策略-->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
</property>
</bean>
但我们知道 org.apache.shiro.authc.pam.ModularRealmAuthenticator#doMultiRealmAuthentication
方法中是要通过 ModularRealmAuthenticator
实例的属性拿到 realms
使用的。为什么这样配置依然好使呢?查看 DefaultWebSecurityManager
的 setRealms
方法:
// org.apache.shiro.mgt.RealmSecurityManager#setRealms
public void setRealms(Collection<Realm> realms) {
if (realms == null) {
throw new IllegalArgumentException("Realms collection argument cannot be null.");
}
if (realms.isEmpty()) {
throw new IllegalArgumentException("Realms collection argument cannot be empty.");
}
this.realms = realms;
// <2>
afterRealmsSet();
}
接着查看 <2>
处的 afterRealmsSet()
方法:
// org.apache.shiro.mgt.AuthorizingSecurityManager#afterRealmsSet
protected void afterRealmsSet() {
super.afterRealmsSet();
if (this.authorizer instanceof ModularRealmAuthorizer) {
((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
}
}
看到这里就可以明了,其实在把 realms
注入给安全管理器 RealmSecurityManager
时,安全管理器同时也把 realms
的引用给了 ModularRealmAuthenticator
的 realms
属性。
评论区