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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 23:10:41  来源:igfitidea点击:

Handle Security exceptions in Spring Boot Resource Server

javaspringsecurityspring-bootspring-security

提问by Pete

How can I get my custom ResponseEntityExceptionHandleror OAuth2ExceptionRendererto handle Exceptions raised by Spring security on a pure resource server?

如何在纯资源服务器上获取自定义ResponseEntityExceptionHandlerOAuth2ExceptionRenderer处理 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 @ControllerAdviceis 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 @ControllerAdvicebecause 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 ResourceServerConfigurerAdapterinstead of WebSecurityConfigurerAdapterin your @Configurationclass. By doing this, you may simply register a custom AuthenticationEntryPointby 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 OAuth2AuthenticationEntryPointthat 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.ResponseEntityExceptionHandlerclass from CustomGlobalExceptionHandler

Spring 3.0 以后,您可以使用@ControllerAdvice(在类级别)并org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandlerCustomGlobalExceptionHandler

@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 authenticationEntryPointas 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 RestTemplatein RemoteTokenServicesconfig which would handle 401 without throwing exception:

所以你需要RestTemplateRemoteTokenServicesconfig 中设置 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));
  }
}