ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-10-30 21:32:39  阅读:199  来源: 互联网

标签:realm apache token 源码 shiro aggregate org Shiro


  之前在研究认证授权的过程中,简单研究过可以有多个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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有