Shiro getAuthenticationInfo()中抛出异常, 全局异常处理却收不到自己抛出的异常

当Shiro在getAuthenticationInfo()中抛出异常时,全局异常处理无法捕获。原因是默认的AuthenticationStrategy导致。解决方案是实现自定义AuthenticationStrategy并正确配置。尝试通过Spring Boot YAML配置、shiro.ini配置、创建Bean方式失败。最终,通过获取SecurityManager的Authenticator并设置自定义Strategy成功解决问题。

该解决思路来自StackOverflow: Apache Shiro: Exception-Handling with Multiple Realms

首先下结论: 这是因为使用了默认的AuthenticationStrategy所致, 只需要为自己的shiro实现自定义的AuthenticationStrategy, 并将其正确配置即可.

public class MyAtLeastOneSuccessfulStrategy extends AtLeastOneSuccessfulStrategy {
   
   
    @Override
    public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
   
   
        if (t != null && t instanceof AuthenticationException){
   
   
            throw (AuthenticationException) t;
        }
        return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
    }
}

配置AuthenticationStrategy的方法如下:

MyAtLeastOneSuccessfulStrategy strategy = new MyAtLeastOneSuccessfulStrategy();
((ModularRealmAuthenticator)(securityManager.getAuthenticator()))
														.setAuthenticationStrategy(strategy);

如果希望知道为何收到的是AuthenticationException, 以及为何要按照上面这么配置. 请看源码分析(我的经历):

为何总是收不到自己throw的异常

doGetAuthenticationInfo方法中

if (user == null)
            throw new UnknownAccountException("账户不存在!");

在调用一堆构造函数后, 进入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());
        }
				
				// 循环遍历realm
        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 {
   
   
										**// 这里调用了自定义Realm的getAuthenticationInfo方法**
                    **info = realm.getAuthenticationInfo(token);**
                } catch (Throwable throwable) {
   
   
										**// 这里采用catch将Realm中抛出的异常直接捕获, 
										// 并且在后续的处理中并没有将该异常重新抛出**
                    t = throwable;
                    if (log.isDebugEnabled()) {
   
   
                        String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
                        log.debug(msg, t);
                    }
                }
### Shiro框架中doGetAuthenticationInfo方法抛出异常无法捕获的解决方案 在Shiro框架中,`doGetAuthenticationInfo` 方法中的异常通常会被包装为 `AuthenticationException` 或其子类,导致自定义异常无法直接被捕获。以下是解决此问题的具体方法: #### 1. **全局异常处理捕获 AuthenticationException** 在 Spring Boot 中,可以使用 `@RestControllerAdvice` 和 `@ExceptionHandler` 来捕获 `AuthenticationException` 及其子类,并通过日志记录或返回友好的错误信息[^3]。 ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(AuthenticationException.class) @ResponseBody public ResponseEntity<ErrorResponseData> handleAuthenticationException(AuthenticationException e) { log.error("认证异常:{}", e.getMessage()); ErrorResponseData errorResponse = new ErrorResponseData(); errorResponse.setCode(401); errorResponse.setMessage("认证失败:" + e.getMessage()); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse); } } ``` #### 2. **自定义异常包装** 如果需要捕获特定的自定义异常(如 `BusinessException` 或 `TokenException`),可以在 `Realm` 中将这些异常包装为 `AuthenticationException` 的子类,并在全局异常处理器中进行区分处理[^4]。 ```java @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String credentials = (String) token.getCredentials(); try { JwtUtil.getInstance().verifyToken(credentials); } catch (TokenExpiredException e) { log.error("Token 已过期", e); throw new ExpiredCredentialsException("Token 已过期"); } catch (SignatureVerificationException e) { log.error("Token 签名无效", e); throw new IncorrectCredentialsException("Token 签名无效"); } catch (JWTVerificationException e) { log.error("Token 无效", e); throw new AuthenticationException("Token 无效"); } // 返回认证信息 return new SimpleAuthenticationInfo(credentials, credentials, getName()); } ``` #### 3. **确保异常传递到全局处理器** Shiro 的过滤器链会拦截异常并将其重新抛出为 `AuthenticationException`,因此需要确保全局异常处理器能够正确捕获这些异常。如果自定义异常未被正确捕获,可能是由于过滤器链配置不当或异常未被正确传递[^1]。 #### 4. **检查 Shiro 过滤器链配置** 确保 Shiro 的过滤器链配置正确,所有需要认证的接口都被拦截,并且异常能够被传递到全局异常处理器[^4]。 ```java @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login", "anon"); // 登录接口无需认证 filterChainDefinitionMap.put("/**", "authc"); // 其他接口需认证 factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return factoryBean; } ``` #### 5. **调试与验证** - 验证 `Realm` 中抛出异常是否能被 `AuthenticationException` 捕获。 - 测试各种异常场景(如 Token 过期、签名无效等),确保全局异常处理器能够返回正确的错误信息。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值