【问题标题】:How to log http request/response body even if OAuth2 validation failed?即使 OAuth2 验证失败,如何记录 http 请求/响应正文?
【发布时间】:2021-01-12 13:34:04
【问题描述】:

我需要将 Spring Boot MVC 应用程序的每个 requestresponse 正文索引(记录)到 Elasticsearch 应用程序中。我在过滤器中实现了日志记录,使其具有最高优先级(顺序 1)。

我的应用程序充当 OAuth2 资源服务器,它在其中验证来自 Auth 服务器的令牌。问题是如果令牌验证失败,请求不会进入过滤器,因此会跳过请求和响应日志索引部分。如果令牌验证失败,当前逻辑在该部分抛出异常:

public class MyJwtAccessTokenConverter extends JwtAccessTokenConverter {
                @Override
                protected Map<String, Object> decode(String token) {
                   //logic to throw token error
           }
   }

即使令牌未经过验证,我也想为请求和响应正文编制索引。我该怎么做?有没有办法在令牌验证之前放置过滤器或任何其他记录请求和响应的特定方式?

编辑: 我的 ResourceServer 配置如下:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Log4j2
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Value("${spring.application.name}")
    public String applicationResourceID;

    @Value(" ${key.config.oauth2.publicKey}")
    private String publicKey;

    @Value("jwt.aes.encrypt.keyValue")
    String jwtAesEncryptionKey;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(
                        "test/**"
                ).permitAll()
                .and().authorizeRequests().anyRequest().authenticated().and()
                .cors().and()
                .csrf().disable()
                .httpBasic().disable()
                .exceptionHandling()
                .authenticationEntryPoint(
                        (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
                )
                .accessDeniedHandler(
                        (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
                );
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
        authenticationEntryPoint.setExceptionTranslator(oauth2ResponseExceptionTranslator());
        resources.authenticationEntryPoint(authenticationEntryPoint);

        OAuth2AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();
        accessDeniedHandler.setExceptionTranslator(oauth2ResponseExceptionTranslator());
        resources.accessDeniedHandler(accessDeniedHandler);

        resources.authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
                .resourceId(applicationResourceID)
                .tokenStore(tokenStore());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        MyJwtAccessTokenConverter converter = new MyJwtAccessTokenConverter(jwtAesEncryptionKey);
        converter.setVerifierKey(publicKey);
        return converter;
    }


    @Bean
    @Profile(value = {"local"})
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }


    @Bean
    public WebResponseExceptionTranslator oauth2ResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {

            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {

                try {

                    log.info("Oauth2ExceptionTranslatorConfiguration", e);

                    ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
                    OAuth2Exception oAuth2ExceptionBody = responseEntity.getBody();
                    HttpStatus statusCode = responseEntity.getStatusCode();

                    OAuth2Exception myOAuth2Response = OAuth2Exception.create(oAuth2ExceptionBody.getOAuth2ErrorCode(), getMessage(oAuth2ExceptionBody));
                    myOAuth2Response.addAdditionalInformation("message", myOAuth2Response.getMessage());
                    myOAuth2Response.addAdditionalInformation("isSuccess", "false");

                    HttpHeaders headers = new HttpHeaders();
                    headers.setAll(responseEntity.getHeaders().toSingleValueMap());

                    return new ResponseEntity<>(myOAuth2Response, headers, statusCode);
                } catch (Exception ex) {
                    log.info("Oauth2ExceptionTranslatorConfiguration", ex);
                    return new ResponseEntity<>(new OAuth2Exception("Error"), null, HttpStatus.INTERNAL_SERVER_ERROR);
                }

            }
        };
    }

    private String getMessage(OAuth2Exception oAuth2Exception) {
        if (oAuth2Exception instanceof InvalidTokenException) {
            return "Invalid Token";
        } else if (oAuth2Exception instanceof InvalidGrantException) {
            return "Invalid Username or password";
        } else if (oAuth2Exception instanceof InvalidRequestException) {
            return "Invalid Request";
        }

        return oAuth2Exception.getOAuth2ErrorCode();
    }

}

【问题讨论】:

  • 您是否尝试在 jwt 授权过滤器之前添加您的过滤器?就像资源服务器配置中的 http.....addFilterBefore(yourcustomfileter, JwtAuthorizationFilter.class) 一样。另外请分享您的过滤器配置
  • @balias 实际上我在项目中找不到JwtAuthorizationFilter 实现。该项目的实现与通常的 Spring Security 略有不同。我已经更新了 ResourceServer 配置。你能调查一下吗?

标签: spring spring-boot spring-mvc spring-security oauth-2.0


【解决方案1】:

这个想法是为此目的使用自定义异常和 ControllerAdvice

所以我的示例代码就像

public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {

        @Override
        protected Map<String, Object> decode(String token) {
            Map<String, Object> pieces = null;
    
            try {
               //
            } catch(InvalidTokenException ex) {
                throw new InvalidTokenException("MY CUSTOM MESSAGE");
            }
    
          //
        }
    }

你也可以使用ControllerAdvice来捕捉你想要的东西

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ResponseStatus(value = HttpStatus.BAD_REQUEST)
  @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class,
      HttpRequestMethodNotSupportedException.class})
  public ResponseEntity<Object> InvalidTokenException(HttpServletRequest req, Exception exception) {
    // log you request and your response
  }
}

如果需要响应,请添加 HttpServletResponse 响应

希望有用

【讨论】:

  • 是的,这可能是解决方案,但我也想在成功的情况下登录(不仅是失败)。此外,除了 InvalidTokenException 之外,我还想记录响应可能还有其他异常。所以,我正在寻找一个常见的地方来记录请求/响应(如过滤器)。我已经更新了 ResourceServer 配置。你能调查一下吗?
【解决方案2】:

我找到了一个简单的解决方案。如果我将过滤器顺序设置为HIGHEST_PRECEDENCE 并带有注释@Order(Ordered.HIGHEST_PRECEDENCE),那么它会在令牌验证之前进入(我可以在其中记录请求)并在我也可以记录响应的所有过滤器/操作之后退出。

【讨论】:

    猜你喜欢
    • 2020-11-07
    • 2017-12-18
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    • 2019-03-16
    • 2019-02-17
    • 1970-01-01
    • 2016-11-21
    相关资源
    最近更新 更多