programing

Spring Boot Resource Server에서 보안 예외 처리

magicmemo 2023. 6. 25. 18:38
반응형

Spring Boot Resource Server에서 보안 예외 처리

어떻게 하면 내 고객을 얻을 수 있는 방법ResponseEntityExceptionHandler또는OAuth2ExceptionRendererSpring 보안에서 제기한 예외를 순수 리소스 서버에서 처리하시겠습니까?

다음을 구현했습니다.

@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

앞서 언급한 바와 같이 요청이 MVC 계층에 도달하기 전에 보안 프레임워크에 의해 거부됩니다.@ControllerAdvice여기서는 옵션이 아닙니다.

Spring Security 프레임워크에는 다음과 같은 세 가지 인터페이스가 있습니다.

  • 기관.스프링 골조보안.웹.인증인증성공 처리기
  • 기관.스프링 골조보안.웹.인증인증 실패 처리기
  • 기관.스프링 골조보안.웹.액세스액세스 거부 처리기

성공적인 로그인, 실패한 로그인, 부족한 권한으로 보호된 리소스에 액세스 시도 등 다양한 이벤트에 대해 전송되는 응답을 사용자 지정하기 위해 각 인터페이스의 구현을 생성할 수 있습니다.

다음은 로그인 시도 실패 시 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또한 확장하는 것이 편리할 수도 있습니다.ResourceServerConfigurerAdapterWebSecurityConfigurerAdapter의 신의에@Configurationclass. 래스를 간단하게 할 수 . 이렇게 하면 사용자 정의를 등록할 수 있습니다.AuthenticationEntryPoint를 하여.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 정의를하는 동안할 수 할 수 .AuthenticationEntryPoint특히 오류 관련 세부 정보가 있는 "WWW-Authenticate" 헤더를 추가합니다.

Spring 핸들러 예: "Spring MVC Exception")을 할 수 .@ControllerAdvice스프링 보안 필터는 스프링 MVC보다 훨씬 이전에 시작되기 때문입니다.

원격으로 리소스 서버 구성과 유사한 구성으로 토큰 유효성 검사 URL을 사용하는 경우권한이 없는 경우 HTTP 상태 401을 반환하는 Spring Security Oauth2의 토큰 서비스:

@Primary
@Bean
public RemoteTokenServices tokenService() {
    RemoteTokenServices tokenService = new RemoteTokenServices();
    tokenService.setCheckTokenEndpointUrl("https://token-validation-url.com");
    tokenService.setTokenName("token");
    return tokenService;
}

정의 authenticationEntryPoint다른 답변에 설명된 바와 같이 (https://stackoverflow.com/a/44372313/5962766) 은 원격으로 작동하지 않습니다.토큰 서비스는 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);
                }
            }
        });
}

그래서 당신은 커스텀을 설정해야 합니다.RestTemplateRemoteTokenServices던지지 :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;
}

그리고 HttpComponentClientHttpRequestFactory에 대한 종속성을 추가합니다.

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>

OAuth2ExceptionRender는 권한 부여 서버용입니다.정답은 이 게시물에 자세히 설명된 것처럼 처리할 가능성이 높습니다(즉, oauth라는 것을 무시하고 다른 봄 보안 인증 메커니즘처럼 처리합니다). https://stackoverflow.com/a/26502321/5639571

물론 리소스 끝점에 도달하기 전에 던져지는 oauth 관련 예외가 발생하지만 리소스 끝점에서 발생하는 모든 예외에는 @ExceptionHandler 메서드가 필요합니다.

된 답변을 OAuth2ResourceServerJWT 특별한 없이 JWT 인의경우, 특별구없자로체으등때다문록니입을 입니다.BearerTokenAuthenticationEntryPoint그리고 우리가 설정한 것을 무시합니다..exceptionHandling().authenticationEntryPoint()

에서.WebSecurityConfigurerAdapter다음이 있습니다.

   @Autowired
   private AuthenticationFailureHandler authenticationFailureHandler;

   protected void configure(HttpSecurity http) throws Exception {
       http
                // ... all the usual stuff ...
                // configure OAuth2 (OIDC) JWT and set a custom authentication failure handler
                .oauth2ResourceServer((resourceServer) -> resourceServer
                        .jwt().and()
                        .authenticationEntryPoint(authenticationFailureHandler));
    }

AuthenticationFailureHandler에서 제안한 코드화되어 있습니다.

@Component
public class AuthenticationFailureHandler implements AuthenticationEntryPoint {

    public AuthenticationFailureHandler() {
    }
    
    // Autowire our own CustomExceptionHandler: must be qualified because Spring Boot has others in the classpath
    @Autowired
    @Qualifier("handlerExceptionResolver") 
    private HandlerExceptionResolver resolver; 

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws
            IOException {
        resolver.resolveException(request, response, null, authException);
    }
}

우리의 관습에서ExceptionHandler(위에서 자동 배선되었지만 클래스 이름으로 명시적으로 언급되지 않음) 인증 방법을 추가합니다.예외 처리:

    @ExceptionHandler(value = {AuthenticationException.class})
    protected ResponseEntity<?> handleAuthenticationException(RuntimeException ex, WebRequest request) {

        return ... something ... // create custom error response here
    }

mvc에 할 수 .@ControllerAdvice

@Component
public class AuthExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {

    private static final Logger LOG = LoggerFactory.getLogger(AuthExceptionHandler.class);

    private final HandlerExceptionResolver resolver;

    @Autowired
    public AuthExceptionHandler(@Qualifier("handlerExceptionResolver") final HandlerExceptionResolver resolver) {
        this.resolver = resolver;
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        LOG.error("Responding with unauthorized error. Message - {}", authException.getMessage());
        resolver.resolveException(request, response, null, authException);
    }

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        LOG.error("Responding with access denied error. Message - {}", accessDeniedException.getMessage());
        resolver.resolveException(request, response, null, accessDeniedException);
    }
}

다음 그다음을사용여예정의다외니합를하를 합니다.@ControllerAdvice글로벌 예외 처리기를 한 곳에서 관리할 수 있습니다.

이것은 가능합니다.원래 질문은 커스텀 JSON 응답을 반환해야 하는 REST 컨트롤러에 대한 것이므로, 저에게 효과가 있었던 전체 답변을 단계적으로 작성하겠습니다.무엇보다도, 당신은 이 일을 처리할 수 없을 것 같습니다.@ControllerAdvice그것은 확장되는ControllResponseEntityExceptionHandler확장할 수 있는 별도의 핸들러가 필요합니다.AccessDeniedHandler다음 단계를 수행합니다.

1을 확장하는 지정 핸들러 : " " " " " "AccessDeniedHandler

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {

    private static final String JSON_TYPE = "application/json";
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException {
        MyErrorList errors = new MyErrorList();
        errors.addError(new MyError("", "You do not have permission to access this resource."));

        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setContentType(JSON_TYPE);
        OutputStream output = response.getOutputStream();
        ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(output, errors);
        output.flush();
    }
}

위의 'MyError'는 오류 json 구조를 나타내는 간단한 POJO이고, 'MyErrorList'는 'MyError' 목록을 포함하는 또 다른 POJO입니다.

2단계: 위에서 만든 처리기를 보안 구성에 삽입합니다.

@Autowired
private VOMSAccessDeniedHandler accessDeniedHandler;  

합니다.accessDeniedHandler할 수 있습니다.

.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)

2단계 3단계사용하면 다음과 같은 이점을 얻을 수 있습니다.SecurityConfiguration다음과 같이 보여야 합니다(이 답변의 길이를 단축하기 위해 이 문제와 관련이 없는 코드를 생략합니다).

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAccessDeniedHandler accessDeniedHandler;

    // Other stuff

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/register").permitAll()
                .antMatchers("/authenticate").permitAll()
                .antMatchers("/public").permitAll()
                .anyRequest().authenticated()
                .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

3.0스링프 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);
}

언급URL : https://stackoverflow.com/questions/41140669/handle-security-exceptions-in-spring-boot-resource-server

반응형