Java 处理 Spring Boot Resource Server 中的安全异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/41140669/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Handle Security exceptions in Spring Boot Resource Server
提问by Pete
How can I get my custom ResponseEntityExceptionHandler
or OAuth2ExceptionRenderer
to handle Exceptions raised by Spring security on a pure resource server?
如何在纯资源服务器上获取自定义ResponseEntityExceptionHandler
或OAuth2ExceptionRenderer
处理 Spring 安全性引发的异常?
We implemented a
我们实施了一个
@ControllerAdvice
@RestController
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
so whenever there is an error on the resource server we want it to answer with
所以每当资源服务器上出现错误时,我们希望它回答
{
"message": "...",
"type": "...",
"status": 400
}
The resource server uses the application.properties setting:
资源服务器使用 application.properties 设置:
security.oauth2.resource.userInfoUri: http://localhost:9999/auth/user
to authenticate and authorize a request against our auth server.
对我们的身份验证服务器进行身份验证和授权请求。
However any spring security error will always bypass our exception handler at
但是,任何 spring 安全错误将始终绕过我们的异常处理程序
@ExceptionHandler(InvalidTokenException.class)
public ResponseEntity<Map<String, Object>> handleInvalidTokenException(InvalidTokenException e) {
return createErrorResponseAndLog(e, 401);
}
and produce either
并生产
{
"timestamp": "2016-12-14T10:40:34.122Z",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/templates/585004226f793042a094d3a9/schema"
}
or
或者
{
"error": "invalid_token",
"error_description": "5d7e4ab5-4a88-4571-b4a4-042bce0a076b"
}
So how do I configure the security exception handling for a resource server? All I ever find are examples on how to customize the Auth Server by implementing a custom OAuth2ExceptionRenderer
. But I can't find where to wire this to the resource server's security chain.
那么如何为资源服务器配置安全异常处理呢?我所找到的只是关于如何通过实现自定义OAuth2ExceptionRenderer
. 但是我找不到将它连接到资源服务器的安全链的位置。
Our only configuration/setup is this:
我们唯一的配置/设置是这样的:
@SpringBootApplication
@Configuration
@ComponentScan(basePackages = {"our.packages"})
@EnableAutoConfiguration
@EnableResourceServer
采纳答案by Alan Hay
As noted in previous comments the request is rejected by the security framework before it reaches the MVC layer so @ControllerAdvice
is not an option here.
如之前的评论中所述,请求在到达 MVC 层之前被安全框架拒绝,因此@ControllerAdvice
这里不是一个选项。
There are 3 interfaces in the Spring Security framework that may be of interest here:
Spring Security 框架中有 3 个接口,您可能对此感兴趣:
- org.springframework.security.web.authentication.AuthenticationSuccessHandler
- org.springframework.security.web.authentication.AuthenticationFailureHandler
- org.springframework.security.web.access.AccessDeniedHandler
- org.springframework.security.web.authentication.AuthenticationSuccessHandler
- org.springframework.security.web.authentication.AuthenticationFailureHandler
- org.springframework.security.web.access.AccessDeniedHandler
You can create implementations of each of these Interfaces in order to customize the response sent for various events: successful login, failed login, attempt to access protected resource with insufficient permissions.
您可以创建这些接口中的每一个的实现,以便自定义为各种事件发送的响应:成功登录、失败登录、尝试访问权限不足的受保护资源。
The following would return a JSON response on unsuccessful login attempt:
以下将在登录尝试失败时返回 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.Hymanson.databind.ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(out, data);
out.flush();
}
}
You also need to register your implementation(s) with the Security framework. In Java config this looks like the below:
您还需要向安全框架注册您的实现。在 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();
}
}
回答by so-random-dude
You are not able to make use of Spring MVC Exception handler annotations such as @ControllerAdvice
because spring security filters kicks in much before Spring MVC.
您无法使用 Spring MVC 异常处理程序注释,例如@ControllerAdvice
因为 Spring 安全过滤器在 Spring MVC 之前就开始使用了。
回答by Nikolai Luthman
OAuth2ExceptionRenderer is for an Authorization Server. The correct answer is likely to handle it like detailed in this post (that is, ignore that it's oauth and treat it like any other spring security authentication mechanism): https://stackoverflow.com/a/26502321/5639571
OAuth2ExceptionRenderer 用于授权服务器。正确答案可能会像这篇文章中详述的那样处理它(即,忽略它是 oauth 并将其视为任何其他 spring 安全身份验证机制):https: //stackoverflow.com/a/26502321/5639571
Of course, this will catch oauth related exceptions (which are thrown before you reach your resource endpoint), but any exceptions happening within your resource endpoint will still require an @ExceptionHandler method.
当然,这将捕获与 oauth 相关的异常(在到达资源端点之前抛出),但是资源端点内发生的任何异常仍然需要 @ExceptionHandler 方法。
回答by Vladimir Salin
In case if you're using @EnableResourceServer
, you may also find convenient to extend ResourceServerConfigurerAdapter
instead of WebSecurityConfigurerAdapter
in your @Configuration
class. By doing this, you may simply register a custom AuthenticationEntryPoint
by overriding configure(ResourceServerSecurityConfigurer resources)
and using resources.authenticationEntryPoint(customAuthEntryPoint())
inside the method.
如果您正在使用@EnableResourceServer
,您可能还会发现扩展ResourceServerConfigurerAdapter
而不是WebSecurityConfigurerAdapter
在您的@Configuration
类中很方便。通过这样做,您可以简单地AuthenticationEntryPoint
通过在方法内部覆盖configure(ResourceServerSecurityConfigurer resources)
和使用来注册自定义resources.authenticationEntryPoint(customAuthEntryPoint())
。
Something like this:
像这样的东西:
@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();
}
}
There's also a nice OAuth2AuthenticationEntryPoint
that can be extended (since it's not final) and partially re-used while implementing a custom AuthenticationEntryPoint
. In particular, it adds "WWW-Authenticate" headers with error-related details.
还有一个很好的OAuth2AuthenticationEntryPoint
可以扩展(因为它不是最终的)并在实现自定义AuthenticationEntryPoint
. 特别是,它添加了带有错误相关详细信息的“WWW-Authenticate”标头。
回答by Rupesh Patil
Spring 3.0 Onwards,You can use @ControllerAdvice
(At Class Level) and extends org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
class from CustomGlobalExceptionHandler
Spring 3.0 以后,您可以使用@ControllerAdvice
(在类级别)并org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
从CustomGlobalExceptionHandler
@ExceptionHandler({com.test.CustomException1.class,com.test.CustomException2.class})
public final ResponseEntity<CustomErrorMessage> customExceptionHandler(RuntimeException ex){
return new ResponseEntity<CustomErrorMessage>(new CustomErrorMessage(false,ex.getMessage(),404),HttpStatus.BAD_REQUEST);
}
回答by Justas
If you're using token validation URL with config similar to Configuring resource server with RemoteTokenServices in Spring Security Oauth2which returns HTTP status 401 in case of unauthorized:
如果您使用类似于在 Spring Security Oauth2 中使用 RemoteTokenServices 配置资源服务器的配置使用令牌验证 URL,它会在未经授权的情况下返回 HTTP 状态 401:
@Primary
@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("https://token-validation-url.com");
tokenService.setTokenName("token");
return tokenService;
}
Implementing custom authenticationEntryPoint
as described in other answers (https://stackoverflow.com/a/44372313/5962766) won't work because RemoteTokenServiceuse 400 status and throws unhandled exceptions for other statuses like 401:
authenticationEntryPoint
按照其他答案 ( https://stackoverflow.com/a/44372313/5962766) 中的描述实现自定义将不起作用,因为RemoteTokenService使用 400 状态并为 401 等其他状态抛出未处理的异常:
public RemoteTokenServices() {
restTemplate = new RestTemplate();
((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
@Override
// Ignore 400
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400) {
super.handleError(response);
}
}
});
}
So you need to set custom RestTemplate
in RemoteTokenServices
config which would handle 401 without throwing exception:
所以你需要RestTemplate
在RemoteTokenServices
config 中设置 custom来处理 401 而不抛出异常:
@Primary
@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("https://token-validation-url.com");
tokenService.setTokenName("token");
RestOperations restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
@Override
// Ignore 400 and 401
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
super.handleError(response);
}
}
});
}
tokenService.setRestTemplate(restTemplate);
return tokenService;
}
And add dependency for HttpComponentsClientHttpRequestFactory:
并为HttpComponentsClientHttpRequestFactory添加依赖项:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
回答by Pranay
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse res,
AuthenticationException authException) throws IOException, ServletException {
ApiException ex = new ApiException(HttpStatus.FORBIDDEN, "Invalid Token", authException);
ObjectMapper mapper = new ObjectMapper();
res.setContentType("application/json;charset=UTF-8");
res.setStatus(403);
res.getWriter().write(mapper.writeValueAsString(ex));
}
}