捕获 AuthenticationProvider 中抛出的异常

Posted

技术标签:

【中文标题】捕获 AuthenticationProvider 中抛出的异常【英文标题】:Catching exception thrown in AuthenticationProvider 【发布时间】:2017-10-14 08:26:54 【问题描述】:

我正在实施自定义“AuthenticationProvider”。如果未通过身份验证,我将在“身份验证”函数中引发异常,如下所示。

public class DelegatingLdapAuthenticationProvider implements AuthenticationProvider 

    private ActiveDirectoryLdapAuthenticationProvider primaryProvider;
    private List<ActiveDirectoryLdapAuthenticationProvider> secondaryProviders = new ArrayList<>();

    public DelegatingLdapAuthenticationProvider() 

    

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException 
        Authentication result = null;
        AuthenticationException exception = null;
        try 
            result = primaryProvider.authenticate(authentication);
         catch (AuthenticationException e) 
            exception = e;
            for (ActiveDirectoryLdapAuthenticationProvider secondaryProvider : secondaryProviders) 
                try 
                    result = secondaryProvider.authenticate(authentication);
                    if (result.isAuthenticated()) 
                            break;
                    
                 catch (AuthenticationException e1) 
                            exception = e;
                
            
        
        if (result == null || !result.isAuthenticated()) 
            throw exception;
    

    return result;

我有如下所示的全局异常处理程序。

@ControllerAdvice
public class GlobalExceptionHandler 

    @ExceptionHandler(NoPermissionException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    public Map<String, String> noPermission(NoPermissionException e) 
        return createErrorResponse(e, "Don't have permissions");
    

    @ExceptionHandler(Exception.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, String> exceptionInProcessing(Exception e) 
        return createErrorResponse(e, "Unable to process. Unknown error occurred: " + e.getMessage());
    

    private Map<String, String> createErrorResponse(Exception e, String errorMessage) 
        Map<String, String> errorResponse = new HashMap<>();
        errorResponse.put("message", errorMessage);
        errorResponse.put("reason", e.toString());
        return errorResponse;
    

当“authenticate”函数内部抛出异常时,不会调用全局异常处理程序。对于所有其他例外情况,它都被调用。我想在全局异常处理程序中捕获异常并返回自定义错误消息。我怎样才能做到这一点?任何帮助表示赞赏。提前致谢。

【问题讨论】:

您是否确实验证了异常被抛出或您的提供程序被首先调用? 【参考方案1】:

GlobalExceptionHandler用于控制器异常处理程序,但AuthenticationProvider仍在过滤器中,如果要处理AuthenticationException,则需要处理它以实现AuthenticationEntryPoint并覆盖commence方法.

public void commence(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException authException) throws IOException, ServletException

AuthenticationExceptionAccessDeniedException 已被 ExceptionTranslationFilter 处理。你只需要注入AuthenticationEntryPointAccessDeniedHandler(处理AccessDeniedException

或者您可以在过滤器中捕获这些异常,然后在文件管理器中处理它,例如AbstractAuthenticationProcessingFilter 中的AuthenticationFailureHandler

【讨论】:

【参考方案2】:

在控制器异常处理程序有机会捕获异常之前调用身份验证提供程序。

您可以覆盖AuthenticationFailureHandler 以处理安全过滤器链级别的异常,请查看examples

documentation中描述的行为:

过滤器调用配置的AuthenticationManager来处理每个认证请求。认证成功或认证失败后的目的地分别由 AuthenticationSuccessHandler 和 AuthenticationFailureHandler 策略接口控制。过滤器具有允许您设置这些属性的属性,以便您可以完全自定义行为

【讨论】:

【参考方案3】:

正如@chaoluo 已经说过的,您需要实现AuthenticationEntryPoint 并覆盖commence 方法。如果要返回错误 JSON 对象,可以执行以下操作:

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException 

    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    //create errorObj
    PrintWriter writer = response.getWriter();
    mapper.writeValue(writer, errorObj);
    writer.flush();

【讨论】:

【参考方案4】:

补充@chaoluo 答案:

    实现AuthenticationEntryPoint接口并通过HandlerExceptionResolver解决异常:
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint, AccessDeniedHandler 

    @Autowired
    private HandlerExceptionResolver resolver;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) 
        resolver.resolveException(request, response, null, exception);
    


    RestAuthenticationEntryPoint 注入到您的WebSecurityConfigurerAdapter 实现中并将其用作authenticationEntryPoint:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private RestAuthenticationEntryPoint authenticationEntryPoint;

    @Override
    public void configure(HttpSecurity http) throws Exception 
        http
                .csrf().disable()
                .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(authenticationEntryPoint)
                .and()
                    .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    

    现在因为我们通过 HandlerExceptionResolver 解决了异常,我们可以使用典型的 Spring Web 错误处理,使用 @ControllerAdvice@ExceptionHandler 注释:
@RestControllerAdvice
public abstract class ErrorsControllerAdvice 


    @ExceptionHandler
    public ResponseEntity<?> handleException(Throwable exception, WebRequest webRequest, Locale locale) 

        return ResponseEntity.status(HttpStatus.UNAUTHORIZED);
    


【讨论】:

以上是关于捕获 AuthenticationProvider 中抛出的异常的主要内容,如果未能解决你的问题,请参考以下文章

带有自定义 AuthenticationProvider 的 spring security 给出了拒绝访问错误

如何使用不同的 AuthenticationProvider 过滤 SPRING SECURITY

Spring Security Java Config - 自定义 AuthenticationProvider 和 UserDetailsS​​ervice

AuthenticationManager, ProviderManager 和 AuthenticationProvider

spring security,UserDetailsS​​ervice,authenticationProvider,密码编码器..我迷路了

使用多个 AuthenticationProvider 时如何从 PreAuthenticatedAuthenticationProvider 重定向 UsernameNotFoundExceptio