处理Spring Boot Resource Server中的安全异常

如何自定义或处理 Spring 安全在纯资源服务器上引发的异常?ResponseEntityExceptionHandlerOAuth2ExceptionRenderer

我们实施了一个

@ControllerAdvice
@RestController
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

因此,每当资源服务器上出现错误时,我们都希望它用

{
  "message": "...",
  "type": "...",
  "status": 400
}

资源服务器使用 application.properties 设置:

security.oauth2.resource.userInfoUri: http://localhost:9999/auth/user

对针对我们的身份验证服务器的请求进行身份验证和授权。

但是,任何弹簧安全错误将始终绕过我们的异常处理程序

    @ExceptionHandler(InvalidTokenException.class)
    public ResponseEntity<Map<String, Object>> handleInvalidTokenException(InvalidTokenException e) {
        return createErrorResponseAndLog(e, 401);
    }

并生产

{
  "timestamp": "2016-12-14T10:40:34.122Z",
  "status": 403,
  "error": "Forbidden",
  "message": "Access Denied",
  "path": "/api/templates/585004226f793042a094d3a9/schema"
}

{
  "error": "invalid_token",
  "error_description": "5d7e4ab5-4a88-4571-b4a4-042bce0a076b"
}

那么,如何为资源服务器配置安全异常处理呢?我所找到的只是有关如何通过实现自定义来自定义身份验证服务器的示例。但是我找不到将其连接到资源服务器的安全链的位置。OAuth2ExceptionRenderer

我们唯一的配置/设置是这样的:

@SpringBootApplication
@Configuration
@ComponentScan(basePackages = {"our.packages"})
@EnableAutoConfiguration
@EnableResourceServer

答案 1

如前面的注释中所述,请求在到达 MVC 层之前被安全框架拒绝,因此此处不是一个选项。@ControllerAdvice

Spring Security框架中有3个接口可能会在这里引起人们的兴趣:

  • org.springframework.security.web.authentication.AuthenticationSuccessHandler
  • org.springframework.security.web.authentication.AuthenticationFailureHandler
  • org.springframework.security.web.access.AccessDeniedHandler

您可以创建每个接口的实现,以便自定义为各种事件发送的响应:成功登录,登录失败,尝试访问权限不足的受保护资源。

登录尝试失败时,以下内容将返回 JSON 响应:

@Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler
{
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException ex) throws IOException, ServletException
  {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    
    Map<String, Object> data = new HashMap<>();
    data.put("timestamp", new Date());
    data.put("status",HttpStatus.FORBIDDEN.value());
    data.put("message", "Access Denied");
    data.put("path", request.getRequestURL().toString());
    
    OutputStream out = response.getOutputStream();
    com.fasterxml.jackson.databind.ObjectMapper mapper = new ObjectMapper();
    mapper.writeValue(out, data);
    out.flush();
  }
}

您还需要向安全框架注册您的实现。在Java配置中,这如下所示:

@Configuration
@EnableWebSecurity
@ComponentScan("...")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
  @Override
  public void configure(HttpSecurity http) throws Exception
  {
    http
       .addFilterBefore(corsFilter(), ChannelProcessingFilter.class)
       .logout()
       .deleteCookies("JESSIONID")
       .logoutUrl("/api/logout")
       .logoutSuccessHandler(logoutSuccessHandler())
       .and()
       .formLogin()
       .loginPage("/login")
       .loginProcessingUrl("/api/login")
       .failureHandler(authenticationFailureHandler())
       .successHandler(authenticationSuccessHandler())
       .and()
       .csrf()
       .disable()
       .exceptionHandling()
       .authenticationEntryPoint(authenticationEntryPoint())
       .accessDeniedHandler(accessDeniedHandler());
  }

  /**
   * @return Custom {@link AuthenticationFailureHandler} to send suitable response to REST clients in the event of a
   *         failed authentication attempt.
   */
  @Bean
  public AuthenticationFailureHandler authenticationFailureHandler()
  {
    return new RestAuthenticationFailureHandler();
  }

  /**
   * @return Custom {@link AuthenticationSuccessHandler} to send suitable response to REST clients in the event of a
   *         successful authentication attempt.
   */
  @Bean
  public AuthenticationSuccessHandler authenticationSuccessHandler()
  {
    return new RestAuthenticationSuccessHandler();
  }

  /**
   * @return Custom {@link AccessDeniedHandler} to send suitable response to REST clients in the event of an attempt to
   *         access resources to which the user has insufficient privileges.
   */
  @Bean
  public AccessDeniedHandler accessDeniedHandler()
  {
    return new RestAccessDeniedHandler();
  }
}

答案 2

如果您使用的是 ,您可能还会发现扩展而不是在课堂上方便。通过执行此操作,您只需通过在方法内部重写和使用来注册自定义。@EnableResourceServerResourceServerConfigurerAdapterWebSecurityConfigurerAdapter@ConfigurationAuthenticationEntryPointconfigure(ResourceServerSecurityConfigurer resources)resources.authenticationEntryPoint(customAuthEntryPoint())

像这样:

@Configuration
@EnableResourceServer
public class CommonSecurityConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.authenticationEntryPoint(customAuthEntryPoint());
    }

    @Bean
    public AuthenticationEntryPoint customAuthEntryPoint(){
        return new AuthFailureHandler();
    }
}

还有一个很好的可以扩展(因为它不是最终的)并在实现自定义时部分重用。特别是,它添加了“WWW-Authenticate”标头,其中包含与错误相关的详细信息。OAuth2AuthenticationEntryPointAuthenticationEntryPoint