编程语言
首页 > 编程语言> > Shiro源码(五)-多realm 的应用

Shiro源码(五)-多realm 的应用

作者:互联网

  之前在研究认证授权的过程中,简单研究过可以有多个realm,下面研究其多个realm 多种认证鉴权方式以及使用。

1. 单Reaml 认证鉴权过程

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() 方法调用链:

2. 多realm 认证认证过程

  在一个普通的web 工程中,一个realm 针对usernamePasswordToken 验证方式足够使用。有的时候需要多种认证方式。 假设我们需要根据微信的uniquecode 进行认证。

1. 新增token

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;
    }
}

2. 新增第二种realm, 验证WechatToken 

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;
    }
}

3. 设置到SecurityManager 中

    // 权限管理,配置主要是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;
    }

4. 新增第二种登录方式

    @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";
    }

5. shiro 配置放开登录地址

        /**
         *  路径 -> 过滤器名称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"); // 所有资源都需要经过验证

6. 测试

  访问 /login 和 /login3 都可以进行认证成功,则证明生效。

7. 原理查看

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 为空则抛出异常。

 

3. 多Realm 授权过程

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, 判断其是否包含指定的角色, 对于权限验证也是类似的机制。

4. 切换多Realm 的认证策略

  上面看到默认的认证策略是 AtLeastOneSuccessfulStrategy, 也就是多个realm 轮询进行认证判断,根据其是否支持指定的token 进行认证处理,最后合并认证结果。 如果想改成所有的认证都必须成功,也就是将认证策略改为:AllSuccessfulStrategy。

默认的三种认证策略是:

1. 源码查看

(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。

2. 切换为AllSuccessfulStrategy

  切换思路主要就是重新设置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的认证策略,默认是最少一个成功, 然后根据不同的策略对认证进行不同的处理。

 

标签:realm,apache,token,源码,shiro,aggregate,org,Shiro
来源: https://www.cnblogs.com/qlqwjy/p/15487483.html