springboot 新版shrio多realm,自定义异常失效解决

一、项目环境

springboot 前后端分离
shiro:1.7.1
多个realm,分别处理小程序,手机号登录

本来项目只有账户密码登录,但是需求加微信绑定登录,加了一个处理微信登录的realm,但是本身单realm的异常提示竟然没有了。

{
  "code": 401,
  "msg": "Authentication token of type [class com.jack.modules.njp.security.oauth2.Oauth2Token] could not be authenticated by any configured realms.  Please ensure that at least one realm can authenticate these tokens."
}

三、先直接说解决方案

自定义ModularRealmAuthenticator

public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {

    private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);

    /**
     * 重写多realm校验方法,避免无法捕获到realm中的自定义异常
     * 取消执行代码 realm.getAuthenticationInfo(token); 的tey catch
     * 多个realm时候,如果其中一个realm抛出异常则直接抛出异常,类似single方法,
     * @param realms
     * @param token
     * @return
     */
    @Override
    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) {
            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;
                info = realm.getAuthenticationInfo(token);
                //当匹配到info就返回不再继续查询
                if (info !=null){
                    return 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;
    }
}

配置到shrio

@Configuration
public class ShiroConfig {
    @Bean("securityManager")
    public SecurityManager securityManager(Oauth2Realm oAuth2Realm, Oauth1Realm oAuth1Realm, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //将自定义的ModularRealmAuthenticator 添加到这里,不能放在realm下面,会失效
        ModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator();
        securityManager.setAuthenticator( authenticator );
        List realms =new ArrayList(2);
        realms.add(oAuth1Realm);
        realms.add(oAuth2Realm);
        securityManager.setRealms(realms);
        securityManager.setSessionManager(sessionManager);
        securityManager.setRememberMeManager(null);
        return securityManager;
    }

四、原因分析

先debug 看执行过程,很多地方都可以打断点
1.自定义过滤器 executeLogin打

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if(StringUtils.isBlank(token)){
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());

            String json = new Gson().toJson(new Result().error(ErrorCode.UNAUTHORIZED));

            httpResponse.getWriter().print(json);

            return false;
        }

        return executeLogin(request, response);
    }

2.自定义realm中doGetAuthenticationInfo 打

@Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
     String accessToken = (String) token.getPrincipal();

     //根据accessToken,查询用户信息
     SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
     //token失效
     if(tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()){
         throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
     }
     /**
      * 增加缓存中的用户实体类,实现临时修改tenant
      */
     SysUserEntity userEntity =null;
     userEntity= (SysUserEntity) redisUtils.get(accessToken);
     if(userEntity ==null){
         //查询用户信息
         userEntity = shiroService.getUser(tokenEntity.getUserId());
     }

     //转换成UserDetail对象
     UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);

     //获取用户对应的部门数据权限
     List<Long> deptIdList = shiroService.getDataScopeList(userDetail.getId());
     userDetail.setDeptIdList(deptIdList);

     //账号锁定
     if(userDetail.getStatus() == 0){
         throw new LockedAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_LOCK));
     }

     SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDetail, accessToken, getName());
     return info;
 }

无论从哪里进都会进到 org.apache.shiro.authc.pam.doMultiRealmAuthentication;
在这里插入图片描述

                try {
                    info = realm.getAuthenticationInfo(token);
                } catch (Throwable var12) {
                    t = var12;
                    if (log.isDebugEnabled()) {
                        String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
                        log.debug(msg, var12);
                    }
                }

在这里无论我们在realm的getAuthenticationInfo方法找那个定义了多少异常都会被捕获,所以我们需要重写他把
try catch去掉就可以

四、总结

耐下心bebug一遍流程,一般都能定位问题的位置,然后百度怎么实现目标。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>