java - 在 Spring Boot 资源服务器中,java处理安全异常

  显示原文与译文双语对照的内容

如何获取自定义 ResponseEntityExceptionHandler 或者 OAuth2ExceptionRenderer 以处理纯资源服务器上 Spring Security 引发的异常?

我们实现了一个


@ControllerAdvice


@RestController


public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {



所以,每当资源服务器出现错误时,我们都希望它能够


{


"message":"...",


"type":"...",


"status": 400


}



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


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



根据授权服务器对请求进行身份验证和授权。

但是,任何 Spring Security 错误都会始终绕过我们的异常处理程序,


 @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



时间:

就像前面提到的,在到达MVC层之前,安全框架拒绝请求,因此 @ControllerAdvice 在这里不是一个选项。

在 Spring Security 框架中有 3个接口可能会引起兴趣:

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

为了自定义针对各种事件发送的响应,你可以创建这些接口的实现: 登录成功,登录失败,尝试访问权限不足的protected 资源。

以下将在失败登录尝试时返回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();


 }


}



如果你使用的是 @EnableResourceServer,你可以能也会发现在 @Configuration 类中扩展 ResourceServerConfigurerAdapter 而不是 WebSecurityConfigurerAdapter 。 通过这样做,你只需重写一个自定义 configure(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();


 }


}



还有一个不错的OAuth2AuthenticationEntryPoint,可以扩展( 因为它不是 final ) 并在实现定制 AuthenticationEntryPoint 时部分被使用。 特别是,它添加了"www认证"头,包含错误。

无法使用 Spring MVC 异常处理程序注释,比如 @ControllerAdvice,因为 Spring Security 过滤器在 spring MVC之前很大程度上启动。

OAuth2ExceptionRenderer用于授权服务器。 正确答案很可能会处理它,如下面的( 也就是说,忽略它是 oauth,并像其他 Spring Security 认证机制一样对待它) 所示: https://stackoverflow.com/a/26502321/5639571

当然,这会捕获oauth相关异常( 在到达资源终结点之前引发),但是在资源端点中发生的任何异常仍然需要 @ExceptionHandler 方法。

...