之前在研究认证授权的过程中,简单研究过可以有多个realm,下面研究其多个realm 多种认证鉴权方式以及使用。
0. realm 认证过程:
可以看出,其本身是一个授权器Authorizer。 其作为授权器使用是需要作为授权其 Authenticator 内部的成员属性调用。
1. 自定义Realm
import com.beust.jcommander.internal.Lists; import com.zd.bx.bean.user.User; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CustomRealm extends AuthorizingRealm { private static final Logger log = LoggerFactory.getLogger(CustomRealm.class); /** * 鉴权 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // getPrimaryPrincipal获取到的是doGetAuthenticationInfo方法最后存进去的user对象 Object primaryPrincipal = principalCollection.getPrimaryPrincipal(); if (primaryPrincipal == null) { return null; } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User currentUser = (User) primaryPrincipal; // 添加角色 authorizationInfo.addRoles(Lists.newArrayList("管理员")); // 添加权限 authorizationInfo.addStringPermissions(Lists.newArrayList("user:manage:*", "dept:manage:*")); log.debug("authorizationInfo roles: {}, permissions: {}", authorizationInfo.getRoles(), authorizationInfo.getStringPermissions()); return authorizationInfo; } /** * 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { if (authenticationToken == null || !(authenticationToken instanceof UsernamePasswordToken)) { return null; } User user = new User(); user.setPassword("111222"); return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); } @Override public boolean supports(AuthenticationToken token) { log.info("token: {}", token); return token != null && UsernamePasswordToken.class.isAssignableFrom(token.getClass()); } }
2. 注入到SecurityManager 中
// 权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 注意realm必须在设置完认证其之后设置, 或者在设置 authenticator 的时候直接设置realm。setRealms 方法会将realm 同时设置到 authenticator 认证器中 securityManager.setRealms(Lists.newArrayList(new CustomRealm())); return securityManager; }
查看其 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; afterRealmsSet(); }
主要的操作包括:设置到SecutityManager 自己的属性内部; 调用 afterRealmsSet() 方法进行后续处理。调用到: org.apache.shiro.mgt.AuthenticatingSecurityManager#afterRealmsSet
protected void afterRealmsSet() { super.afterRealmsSet(); if (this.authenticator instanceof ModularRealmAuthenticator) { ((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms()); } }
可以看到是调用父类方法,然后设置到 authenticator 认证器内部。org.apache.shiro.mgt.RealmSecurityManager#afterRealmsSet: 是设置到缓存器和发布事件
protected void afterRealmsSet() { applyCacheManagerToRealms(); applyEventBusToRealms(); }
3. 调用链查看:
(1) 认证方法 doGetAuthenticationInfo 认证方法调用链:
(2) 授权方法 doGetAuthornizationInfo() 方法调用链:
在一个普通的web 工程中,一个realm 针对usernamePasswordToken 验证方式足够使用。有的时候需要多种认证方式。 假设我们需要根据微信的uniquecode 进行认证。
package com.zd.bx.config.shiro; import org.apache.shiro.authc.AuthenticationToken; public class WechatToken implements AuthenticationToken { private String wechatUniqueName; public WechatToken(String wechatUniqueName) { this.wechatUniqueName = wechatUniqueName; } @Override public Object getPrincipal() { return wechatUniqueName; } @Override public Object getCredentials() { return wechatUniqueName; } public String getWechatUniqueName() { return wechatUniqueName; } }
package com.zd.bx.config.shiro; import com.zd.bx.bean.user.User; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class WechatRealm extends AuthorizingRealm { private static final Logger log = LoggerFactory.getLogger(WechatRealm.class); @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { if (authenticationToken == null || !(authenticationToken instanceof WechatToken)) { return null; } WechatToken wechatToken = (WechatToken) authenticationToken; User user = new User(); user.setPassword(wechatToken.getWechatUniqueName()); return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); } @Override public boolean supports(AuthenticationToken token) { log.info("token: {}", token); return token != null && token instanceof WechatToken; } }
// 权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 注意realm必须在设置完认证其之后设置, 或者在设置 authenticator 的时候直接设置realm。setRealms 方法会将realm 同时设置到 authenticator 认证器中 securityManager.setRealms(Lists.newArrayList(new CustomRealm(), new WechatRealm())); return securityManager; }
@GetMapping("/login2") public String login2() { Subject subject = SecurityUtils.getSubject(); AuthenticationToken generateToken = new UsernamePasswordToken("zs", "111222"); subject.login(generateToken); return "success"; } @GetMapping("/login3") public String login3() { Subject subject = SecurityUtils.getSubject(); WechatToken wechatToken = new WechatToken("qiaozhi"); subject.login(wechatToken); return "success"; }
/** * 路径 -> 过滤器名称1[参数1,参数2,参数3...],过滤器名称2[参数1,参数2...]... * 自定义配置(前面是路径, 后面是具体的过滤器名称加参数,多个用逗号进行分割,过滤器参数也多个之间也是用逗号分割)) * 有的过滤器不需要参数,比如anon, authc, shiro 在解析的时候接默认解析一个数组为 [name, null] */ FILTER_CHAIN_DEFINITION_MAP.put("/test2", "anon"); // 测试地址 FILTER_CHAIN_DEFINITION_MAP.put("/login2", "anon"); // 登陆地址 FILTER_CHAIN_DEFINITION_MAP.put("/login3", "anon"); // 登陆地址 FILTER_CHAIN_DEFINITION_MAP.put("/user/**", "roles[系统管理员,用户管理员],perms[user:manager:*]"); FILTER_CHAIN_DEFINITION_MAP.put("/dept/**", "perms[dept:manage:*]"); FILTER_CHAIN_DEFINITION_MAP.put("/**", "authc"); // 所有资源都需要经过验证
访问 /login 和 /login3 都可以进行认证成功,则证明生效。
1. org.apache.shiro.authc.pam.ModularRealmAuthenticator#doAuthenticate 获取认证信息
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); Collection<Realm> realms = getRealms(); if (realms.size() == 1) { return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken); } else { return doMultiRealmAuthentication(realms, authenticationToken); } }
2. org.apache.shiro.authc.pam.ModularRealmAuthenticator#doMultiRealmAuthentication
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) { 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) { try { aggregate = strategy.beforeAttempt(realm, token, aggregate); } catch (ShortCircuitIterationException shortCircuitSignal) { // Break from continuing with subsequnet realms on receiving // short circuit signal from strategy break; } 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); } } 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() 获取认证策略, 默认是AtLeastOneSuccessfulStrategy 至少有一个成功策略,总共的策略有:
2》 strategy.beforeAllAttempts(realms, token); 调用到 org.apache.shiro.authc.pam.AbstractAuthenticationStrategy#beforeAllAttempts 创建了一个SimpleAuthenticationInfo 对象。
public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException { return new SimpleAuthenticationInfo(); }
3》遍历Realm 进行处理:
(1) 如果支持调用org.apache.shiro.authc.pam.AbstractAuthenticationStrategy#beforeAttempt 进行处理之前逻辑:
public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { return aggregate; }
(2) 首先调用 realm.supports(token) 判断是否支持验证指定的token, 不支持直接进行下一个realm。也就是重复 3》 过程
(3) realm.getAuthenticationInfo(token) 获取认证信息,这里会调用到realm, 先从缓存获取,获取不到调用doGetAuthenticationInfo 方法
(4) 调用afterAttempt 重置 aggregate 对象。 会调用到:org.apache.shiro.authc.pam.AbstractAuthenticationStrategy#afterAttempt
public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException { AuthenticationInfo info; if (singleRealmInfo == null) { info = aggregateInfo; } else { if (aggregateInfo == null) { info = singleRealmInfo; } else { info = merge(singleRealmInfo, aggregateInfo); } } return info; }
这里实际就是调用 org.apache.shiro.authc.SimpleAuthenticationInfo#merge 合并两个 info。 实际就是将单个realm 获取到的认证信息合并到aggregate 属性中
4》 最后的realm 处理完之后调用 strategy.afterAllAttempts(token, aggregate);, 这里调用到 org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy#afterAllAttempts 重写了父类的方法:
public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { //we know if one or more were able to successfully authenticate if the aggregated account object does not //contain null or empty data: if (aggregate == null || isEmpty(aggregate.getPrincipals())) { throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " + "could not be authenticated by any configured realms. Please ensure that at least one realm can " + "authenticate these tokens."); } return aggregate; }
也就是验证认证是否成功,如果走完所有的realm 都不成功则抛出异常。
这里可以看到针对org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy 策略的多realm 认证的方式是: 遍历所有的realm, 如果其 supports 返回true, 也就是支持验证该token。 进行token 的认证, 认证完之后将认证的信息合并到一个统一的SimpleAuthenticationInfo 对象aggregate 内部。 如果最后的aggregate 为空,或者其内部的认证对象Principals 为空则抛出异常。
1.授权过程会调用到: org.apache.shiro.authz.ModularRealmAuthorizer#hasRole
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) { assertRealmsConfigured(); for (Realm realm : getRealms()) { if (!(realm instanceof Authorizer)) continue; if (((Authorizer) realm).hasRole(principals, roleIdentifier)) { return true; } } return false; }
2. 这里实际是调用多个realm, 判断其是否包含指定的角色, 对于权限验证也是类似的机制。
上面看到默认的认证策略是 AtLeastOneSuccessfulStrategy, 也就是多个realm 轮询进行认证判断,根据其是否支持指定的token 进行认证处理,最后合并认证结果。 如果想改成所有的认证都必须成功,也就是将认证策略改为:AllSuccessfulStrategy。
默认的三种认证策略是:
(1) org.apache.shiro.authc.pam.AbstractAuthenticationStrategy
package org.apache.shiro.authc.pam; import org.apache.shiro.authc.*; import org.apache.shiro.realm.Realm; import java.util.Collection; /** * Abstract base implementation for Shiro's concrete <code>AuthenticationStrategy</code> * implementations. * * @since 0.9 */ public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy { /** * Simply returns <code>new {@link org.apache.shiro.authc.SimpleAuthenticationInfo SimpleAuthenticationInfo}();</code>, which supports * aggregating account data across realms. */ public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException { return new SimpleAuthenticationInfo(); } /** * Simply returns the <code>aggregate</code> method argument, without modification. */ public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { return aggregate; } /** * Base implementation that will aggregate the specified <code>singleRealmInfo</code> into the * <code>aggregateInfo</code> and then returns the aggregate. Can be overridden by subclasses for custom behavior. */ public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException { AuthenticationInfo info; if (singleRealmInfo == null) { info = aggregateInfo; } else { if (aggregateInfo == null) { info = singleRealmInfo; } else { info = merge(singleRealmInfo, aggregateInfo); } } return info; } /** * Merges the specified <code>info</code> argument into the <code>aggregate</code> argument and then returns an * aggregate for continued use throughout the login process. * <p/> * This implementation merely checks to see if the specified <code>aggregate</code> argument is an instance of * {@link org.apache.shiro.authc.MergableAuthenticationInfo MergableAuthenticationInfo}, and if so, calls * <code>aggregate.merge(info)</code> If it is <em>not</em> an instance of * <code>MergableAuthenticationInfo</code>, an {@link IllegalArgumentException IllegalArgumentException} is thrown. * Can be overridden by subclasses for custom merging behavior if implementing the * {@link org.apache.shiro.authc.MergableAuthenticationInfo MergableAuthenticationInfo} is not desired for some reason. */ protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) { if( aggregate instanceof MergableAuthenticationInfo ) { ((MergableAuthenticationInfo)aggregate).merge(info); return aggregate; } else { throw new IllegalArgumentException( "Attempt to merge authentication info from multiple realms, but aggregate " + "AuthenticationInfo is not of type MergableAuthenticationInfo." ); } } /** * Simply returns the <code>aggregate</code> argument without modification. Can be overridden for custom behavior. */ public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { return aggregate; } }
(2) AtLeastOneSuccessfulStrategy 主要重写了afterAllAttempts 验证是否认证成功,认证失败抛出异常
package org.apache.shiro.authc.pam; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.PrincipalCollection; /** * <tt>AuthenticationStrategy</tt> implementation that requires <em>at least one</em> configured realm to * successfully process the submitted <tt>AuthenticationToken</tt> during the log-in attempt. * <p/> * <p>This means any number of configured realms do not have to support the submitted log-in token, or they may * be unable to acquire <tt>AuthenticationInfo</tt> for the token, but as long as at least one can do both, this * Strategy implementation will allow the log-in process to be successful. * <p/> * <p>Note that this implementation will aggregate the account data from <em>all</em> successfully consulted * realms during the authentication attempt. If you want only the account data from the first successfully * consulted realm and want to ignore all subsequent realms, use the * {@link FirstSuccessfulStrategy FirstSuccessfulAuthenticationStrategy} instead. * * @see FirstSuccessfulStrategy FirstSuccessfulAuthenticationStrategy * @since 0.2 */ public class AtLeastOneSuccessfulStrategy extends AbstractAuthenticationStrategy { private static boolean isEmpty(PrincipalCollection pc) { return pc == null || pc.isEmpty(); } /** * Ensures that the <code>aggregate</code> method argument is not <code>null</code> and * <code>aggregate.{@link org.apache.shiro.authc.AuthenticationInfo#getPrincipals() getPrincipals()}</code> * is not <code>null</code>, and if either is <code>null</code>, throws an AuthenticationException to indicate * that none of the realms authenticated successfully. */ public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { //we know if one or more were able to successfully authenticate if the aggregated account object does not //contain null or empty data: if (aggregate == null || isEmpty(aggregate.getPrincipals())) { throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " + "could not be authenticated by any configured realms. Please ensure that at least one realm can " + "authenticate these tokens."); } return aggregate; } }View Code
(3) AllSuccessfulStrategy
package org.apache.shiro.authc.pam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.realm.Realm; /** * <tt>AuthenticationStrategy</tt> implementation that requires <em>all</em> configured realms to * <b>successfully</b> process the submitted <tt>AuthenticationToken</tt> during the log-in attempt. * <p/> * <p>If one or more realms do not support the submitted token, or one or more are unable to acquire * <tt>AuthenticationInfo</tt> for the token, this implementation will immediately fail the log-in attempt for the * associated subject (user). * * @since 0.2 */ public class AllSuccessfulStrategy extends AbstractAuthenticationStrategy { /** Private class log instance. */ private static final Logger log = LoggerFactory.getLogger(AllSuccessfulStrategy.class); /** * Because all realms in this strategy must complete successfully, this implementation ensures that the given * <code>Realm</code> {@link org.apache.shiro.realm.Realm#supports(org.apache.shiro.authc.AuthenticationToken) supports} the given * <code>token</code> argument. If it does not, this method throws an * {@link UnsupportedTokenException UnsupportedTokenException} to end the authentication * process immediately. If the realm does support the token, the <code>info</code> argument is returned immediately. */ public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { if (!realm.supports(token)) { String msg = "Realm [" + realm + "] of type [" + realm.getClass().getName() + "] does not support " + " the submitted AuthenticationToken [" + token + "]. The [" + getClass().getName() + "] implementation requires all configured realm(s) to support and be able to process the submitted " + "AuthenticationToken."; throw new UnsupportedTokenException(msg); } return info; } /** * Merges the specified <code>info</code> into the <code>aggregate</code> argument and returns it (just as the * parent implementation does), but additionally ensures the following: * <ol> * <li>if the <code>Throwable</code> argument is not <code>null</code>, re-throws it to immediately cancel the * authentication process, since this strategy requires all realms to authenticate successfully.</li> * <li>neither the <code>info</code> or <code>aggregate</code> argument is <code>null</code> to ensure that each * realm did in fact authenticate successfully</li> * </ol> */ public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info, AuthenticationInfo aggregate, Throwable t) throws AuthenticationException { if (t != null) { if (t instanceof AuthenticationException) { //propagate: throw ((AuthenticationException) t); } else { String msg = "Unable to acquire account data from realm [" + realm + "]. The [" + getClass().getName() + " implementation requires all configured realm(s) to operate successfully " + "for a successful authentication."; throw new AuthenticationException(msg, t); } } if (info == null) { String msg = "Realm [" + realm + "] could not find any associated account data for the submitted " + "AuthenticationToken [" + token + "]. The [" + getClass().getName() + "] implementation requires " + "all configured realm(s) to acquire valid account data for a submitted token during the " + "log-in process."; throw new UnknownAccountException(msg); } log.debug("Account successfully authenticated using realm [{}]", realm); // If non-null account is returned, then the realm was able to authenticate the // user - so merge the account with any accumulated before: merge(info, aggregate); return aggregate; } }
重写了 beforeAttempt 方法, 如果该realm 不支持该token 抛出异常; afterAttempt 也是合并加验证是否认证成功。 确保必须所有realm 都认证成功。
(4) org.apache.shiro.authc.pam.FirstSuccessfulStrategy 第一个成功
public class FirstSuccessfulStrategy extends AbstractAuthenticationStrategy { private boolean stopAfterFirstSuccess; public void setStopAfterFirstSuccess (boolean stopAfterFirstSuccess ) { this.stopAfterFirstSuccess = stopAfterFirstSuccess ; } public boolean getStopAfterFirstSuccess() { return stopAfterFirstSuccess ; } /** * Returns {@code null} immediately, relying on this class's {@link #merge merge} implementation to return * only the first {@code info} object it encounters, ignoring all subsequent ones. */ public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException { return null; } /** * Throws ShortCircuitIterationException if stopAfterFirstSuccess is set and authentication is * successful with a previously consulted realm. * Returns the <code>aggregate</code> method argument, without modification * otherwise. */ public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { if (getStopAfterFirstSuccess() && aggregate != null && !isEmpty(aggregate.getPrincipals())) { throw new ShortCircuitIterationException(); } return aggregate; } private static boolean isEmpty(PrincipalCollection pc) { return pc == null || pc.isEmpty(); } /** * Returns the specified {@code aggregate} instance if is non null and valid (that is, has principals and they are * not empty) immediately, or, if it is null or not valid, the {@code info} argument is returned instead. * <p/> * This logic ensures that the first valid info encountered is the one retained and all subsequent ones are ignored, * since this strategy mandates that only the info from the first successfully authenticated realm be used. */ protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) { if (aggregate != null && !isEmpty(aggregate.getPrincipals())) { return aggregate; } return info != null ? info : aggregate; } }
重写 beforeAllAttempts 返回一个空对象; beforeAttempt 判断如果认证,抛出异常 ShortCircuitIterationException, 上面org.apache.shiro.authc.pam.ModularRealmAuthenticator#doMultiRealmAuthentication 捕捉到异常则结束后续realm 认证; merge 方法只返回单个对象, 不进行merge。
切换思路主要就是重新设置SecurityManager的authenticator。 其中在设置authenticator的过程中需要注意, authenticator 需要设置在设置realm 之前,否则重新设置authenticator 之后不会应用给securityManager 设置的realms, 原因是org.apache.shiro.mgt.RealmSecurityManager#setRealms 设置完调用 afterRealmsSet 给authenticator 设置realms。
下面两种方法原理一样, 只是代码写的不同而已。
方法一:
@Bean public Authenticator authenticator() { ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy()); authenticator.setRealms(Lists.newArrayList(new CustomRealm(), new WechatRealm())); return authenticator; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setAuthenticator(authenticator()); return securityManager; }
方法二:
@Bean public Authenticator authenticator() { ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy()); return authenticator; } // 权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setAuthenticator(authenticator()); // 注意realm必须在设置完认证其之后设置, 或者在设置 authenticator 的时候直接设置realm。setRealms 方法会将realm 同时设置到 authenticator 认证器中 securityManager.setRealms(Lists.newArrayList(new CustomRealm(), new WechatRealm())); return securityManager; }
总结:对于认证和授权的机制还不是太一样。 授权是遍历所有的realm, 判断其是否有指定的角色或者权限; 认证的时候会有一个多realm的认证策略,默认是最少一个成功, 然后根据不同的策略对认证进行不同的处理。