web-dev-qa-db-ja.com

Spring Boot Resource Serverでセキュリティ例外を処理する

カスタムResponseEntityExceptionHandlerまたはOAuth2ExceptionRendererを取得して、純粋なリソースサーバーでSpringセキュリティによって発生した例外を処理するにはどうすればよいですか?

実装しました

@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
15
Pete

前のコメントで述べたように、リクエストは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 configでは、これは以下のようになります。

@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();
  }
}
28
Alan Hay

_@EnableResourceServer_を使用している場合、_@Configuration_クラスでResourceServerConfigurerAdapterの代わりにWebSecurityConfigurerAdapterを拡張すると便利な場合があります。これを行うことで、configure(ResourceServerSecurityConfigurer resources)をオーバーライドし、メソッド内でresources.authenticationEntryPoint(customAuthEntryPoint())を使用することで、単純にカスタムAuthenticationEntryPointを登録できます。

このようなもの:

_@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();
    }
}
_

また、(最終ではないので)拡張し、カスタムAuthenticationEntryPointの実装中に部分的に再利用できるNice _OAuth2AuthenticationEntryPoint_もあります。特に、エラー関連の詳細を含む「WWW-Authenticate」ヘッダーを追加します。

6
Vladimir Salin

スプリングセキュリティフィルターは、Spring MVCよりもずっと前に起動するため、@ControllerAdviceなどのSpring MVC例外ハンドラーアノテーションを使用することはできません。

3
so-random-dude

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;
}

他の回答( https://stackoverflow.com/a/44372313/5962766 )で説明されているカスタムauthenticationEntryPointの実装は、 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);
                }
            }
        });
}

したがって、例外をスローせずに401を処理するRestTemplate configでカスタムRemoteTokenServicesを設定する必要があります。

@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;
}

HttpComponentsClientHttpRequestFactory の依存関係を追加します。

<dependency>
  <groupId>org.Apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>
2
Justas

OAuth2ExceptionRendererは、承認サーバー用です。正しい答えは、この投稿で詳しく説明されているように処理する可能性があります(つまり、oauthであることを無視し、他のスプリングセキュリティ認証メカニズムと同様に扱います): https:// stackoverflow.com/a/26502321/5639571

もちろん、これはoauth関連する例外(リソースエンドポイントに到達する前にスローされる))をキャッチしますが、リソースエンドポイント内で発生する例外には@ExceptionHandlerメソッドが必要です。

1
Nikolai Luthman