websecurityconfiguration antmatcher()在集成测试中不忽略外部api的特定url

9njqaruj  于 2021-07-06  发布在  Java
关注(0)|答案(0)|浏览(270)

我正在spring引导应用程序中实现awscognito安全机制。在启用安全性之后,我遇到了一个针对外部api的现有集成测试的问题。作为测试结果,我收到一个错误:
2020-11-15 18:18:20.033错误12072---[main].c.s.f.awscognitojwtauthenticationfilter:操作无效,找不到令牌mockhttpservletresponse:status=401 error message=null headers=[access control allow origin:“*”,access control allow methods:“post,get,options,put,delete”,access control max age:“3600”,access control allow credentials:“true”,access control allow headers:“content type,authorization”,content type:“application/json”]content type=application/json body={“data”:null,“exception”:{“message”:“jwt handle exception”,“httpstatuscode”:“internal\u server\u error”,“detail”:null}
我的 WebSecurityConfiguration 看起来像:

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

  private CustomAuthenticationProvider authProvider;
  private AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter;
  private AccountControllerExceptionHandler exceptionHandler;
  private static final String LOGIN_URL = "/auth/login";
  private static final String LOGOUT_URL = "/auth/signOut";

  @Autowired
  public WebSecurityConfiguration(
      CustomAuthenticationProvider authProvider,
      AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter,
      AccountControllerExceptionHandler exceptionHandler) {
    this.authProvider = authProvider;
    this.awsCognitoJwtAuthenticationFilter = awsCognitoJwtAuthenticationFilter;
    this.exceptionHandler = exceptionHandler;
  }

  public WebSecurityConfiguration() {
    super(true);
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(authProvider).eraseCredentials(false);
  }

  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  @Override
  public void configure(WebSecurity web) {
    // TokenAuthenticationFilter will ignore the below paths
    web.ignoring().antMatchers("/auth");
    web.ignoring().antMatchers("/auth/**");
    web.ignoring().antMatchers("/v2/api-docs");
    web.ignoring().antMatchers(GET, "/nutrition/api/**");
    web.ignoring().antMatchers(GET, "/**");
    web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
  }

  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
        .addFilterAfter(corsFilter(), ExceptionTranslationFilter.class)
        .exceptionHandling()
        .authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
        .accessDeniedHandler(new RestAccessDeniedHandler())
        .and()
        .anonymous()
        .and()
        .sessionManagement()
        .sessionCreationPolicy(STATELESS)
        .and()
        .authorizeRequests()
        .antMatchers("/auth")
        .permitAll()
        .anyRequest()
        .authenticated()
        .and()
        .addFilterBefore(
            awsCognitoJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
        .formLogin(formLogin -> formLogin.loginProcessingUrl(LOGIN_URL).failureHandler(exceptionHandler))
        .logout(logout -> logout.permitAll().logoutUrl(LOGOUT_URL))
        .csrf(AbstractHttpConfigurer::disable);
  }

  private CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader(ORIGIN);
    config.addAllowedHeader(CONTENT_TYPE);
    config.addAllowedHeader(ACCEPT);
    config.addAllowedHeader(AUTHORIZATION);
    config.addAllowedMethod(GET);
    config.addAllowedMethod(PUT);
    config.addAllowedMethod(POST);
    config.addAllowedMethod(OPTIONS);
    config.addAllowedMethod(DELETE);
    config.addAllowedMethod(PATCH);
    config.setMaxAge(3600L);

    source.registerCorsConfiguration("/v2/api-docs", config);
    source.registerCorsConfiguration("/**", config);

    return new CorsFilter();
  }
}
``` `AwsCognitoJwtAuthenticationFilter` ```
@Slf4j
public class AwsCognitoJwtAuthenticationFilter extends OncePerRequestFilter {

  private static final String ERROR_OCCURRED_WHILE_PROCESSING_THE_TOKEN =
      "Error occured while processing the token";
  private static final String INVALID_TOKEN_MESSAGE = "Invalid Token";

  private final AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor;

  @Autowired private ApplicationContext appContext;

  public AwsCognitoJwtAuthenticationFilter(AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor) {
    this.awsCognitoIdTokenProcessor = awsCognitoIdTokenProcessor;
  }

  private void createExceptionResponse(
      ServletRequest request, ServletResponse response, CognitoException exception)
      throws IOException {
    HttpServletRequest req = (HttpServletRequest) request;
    ExceptionController exceptionController;
    ObjectMapper objMapper = new ObjectMapper();

    exceptionController = appContext.getBean(ExceptionController.class);
    ResponseData<Object> responseData = exceptionController.handleJwtException(req, exception);

    HttpServletResponse httpResponse = CorsHelper.addResponseHeaders(response);

    final HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(httpResponse);
    wrapper.setStatus(HttpStatus.UNAUTHORIZED.value());
    wrapper.setContentType(APPLICATION_JSON_VALUE);
    wrapper.getWriter().println(objMapper.writeValueAsString(responseData));
    wrapper.getWriter().flush();
  }

  @Override
  protected void doFilterInternal(
      HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    Authentication authentication;
    try {
      authentication = awsCognitoIdTokenProcessor.getAuthentication(request);

      SecurityContextHolder.getContext().setAuthentication(authentication);

    } catch (BadJOSEException e) {
      SecurityContextHolder.clearContext();
      log.error(e.getMessage());
      createExceptionResponse(
          request,
          response,
          new CognitoException(
              INVALID_TOKEN_MESSAGE,
              CognitoException.INVALID_TOKEN_EXCEPTION_CODE,
              e.getMessage()));
      return;
    } catch (CognitoException e) {
      SecurityContextHolder.clearContext();
      log.error(e.getMessage());
      createExceptionResponse(
          request,
          response,
          new CognitoException(
              e.getErrorMessage(),
              CognitoException.INVALID_TOKEN_EXCEPTION_CODE,
              e.getDetailErrorMessage()));
      return;
    } catch (Exception e) {
      SecurityContextHolder.clearContext();
      log.error(e.getMessage());
      createExceptionResponse(
          request,
          response,
          new CognitoException(
              ERROR_OCCURRED_WHILE_PROCESSING_THE_TOKEN,
              CognitoException.INVALID_TOKEN_EXCEPTION_CODE,
              e.getMessage()));
      return;
    }

    filterChain.doFilter(request, response);
  }
}
``` `AwsCognitoIdTokenProcessor` ```
@AllArgsConstructor
@NoArgsConstructor
public class AwsCognitoIdTokenProcessor {

  private static final String INVALID_TOKEN = "Invalid Token";
  private static final String NO_TOKEN_FOUND = "Invalid Action, no token found";

  private static final String ROLE_PREFIX = "ROLE_";
  private static final String EMPTY_STRING = "";

  private ConfigurableJWTProcessor<SecurityContext> configurableJWTProcessor;

  private AWSConfig jwtConfiguration;

  private String extractAndDecodeJwt(String token) {
    String tokenResult = token;

    if (token != null && token.startsWith("Bearer ")) {
      tokenResult = token.substring("Bearer ".length());
    }
    return tokenResult;
  }

  @SuppressWarnings("unchecked")
  public Authentication getAuthentication(HttpServletRequest request)
      throws ParseException, BadJOSEException, JOSEException {
    String idToken = request.getHeader(HTTP_HEADER);
    if (idToken == null) {
      throw new CognitoException(
          NO_TOKEN_FOUND,
          NO_TOKEN_PROVIDED_EXCEPTION,
          "No token found in Http Authorization Header");
    } else {

      idToken = extractAndDecodeJwt(idToken);
      JWTClaimsSet claimsSet;

      claimsSet = configurableJWTProcessor.process(idToken, null);

      if (!isIssuedCorrectly(claimsSet)) {
        throw new CognitoException(
            INVALID_TOKEN,
            INVALID_TOKEN_EXCEPTION_CODE,
            String.format(
                "Issuer %s in JWT token doesn't match cognito idp %s",
                claimsSet.getIssuer(), jwtConfiguration.getCognitoIdentityPoolUrl()));
      }

      if (!isIdToken(claimsSet)) {
        throw new CognitoException(
            INVALID_TOKEN, NOT_A_TOKEN_EXCEPTION, "JWT Token doesn't seem to be an ID Token");
      }

      String username = claimsSet.getClaims().get(USER_NAME_FIELD).toString();

      List<String> groups = (List<String>) claimsSet.getClaims().get(COGNITO_GROUPS);
      List<GrantedAuthority> grantedAuthorities =
          convertList(
              groups, group -> new SimpleGrantedAuthority(ROLE_PREFIX + group.toUpperCase()));
      User user = new User(username, EMPTY_STRING, grantedAuthorities);
      return new CognitoJwtAuthentication(user, claimsSet, grantedAuthorities);
    }
  }

  private boolean isIssuedCorrectly(JWTClaimsSet claimsSet) {
    return claimsSet.getIssuer().equals(jwtConfiguration.getCognitoIdentityPoolUrl());
  }

  private boolean isIdToken(JWTClaimsSet claimsSet) {
    return claimsSet.getClaim("token_use").equals("id");
  }

  private static <T, U> List<U> convertList(List<T> from, Function<T, U> func) {
    return from.stream().map(func).collect(Collectors.toList());
  }
}
``` `CognitoJwtAutoConfiguration` ```
@Configuration
@Import(AWSConfig.class)
@ConditionalOnClass({AwsCognitoJwtAuthenticationFilter.class, AwsCognitoIdTokenProcessor.class})
public class CognitoJwtAutoConfiguration {

  private final AWSConfig jwtConfiguration;

  public CognitoJwtAutoConfiguration(AWSConfig jwtConfiguration) {
    this.jwtConfiguration = jwtConfiguration;
  }

  @Bean
  @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
  public CognitoJwtIdTokenCredentialsHolder awsCognitoCredentialsHolder() {
    return new CognitoJwtIdTokenCredentialsHolder();
  }

  @Bean
  public AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor() {
    return new AwsCognitoIdTokenProcessor();
  }

  @Bean
  public CognitoJwtAuthenticationProvider jwtAuthenticationProvider() {
    return new CognitoJwtAuthenticationProvider();
  }

  @Bean
  public AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter() {
    return new AwsCognitoJwtAuthenticationFilter(awsCognitoIdTokenProcessor());
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  @Bean
  public ConfigurableJWTProcessor configurableJWTProcessor() throws MalformedURLException {
    ResourceRetriever resourceRetriever =
        new DefaultResourceRetriever(CONNECTION_TIMEOUT, READ_TIMEOUT);
    // https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.
    URL jwkSetURL = new URL(jwtConfiguration.getJwkUrl());
    // Creates the JSON Web Key (JWK)
    JWKSource keySource = new RemoteJWKSet(jwkSetURL, resourceRetriever);
    ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor();
    JWSKeySelector keySelector = new JWSVerificationKeySelector(RS256, keySource);
    jwtProcessor.setJWSKeySelector(keySelector);
    return jwtProcessor;
  }

  @Bean
  public AWSCognitoIdentityProvider awsCognitoIdentityProvider() {
    return AWSCognitoIdentityProviderClientBuilder.standard()
        .withRegion(Regions.EU_CENTRAL_1)
        .withCredentials(getCredentialsProvider())
        .build();
  }

  @Bean
  public AWSCredentialsProvider getCredentialsProvider() {
    return new ClasspathPropertiesFileCredentialsProvider();
  }
}

我想将我的控制器url排除在需要授权的端点之外。
基于视觉测试的控制器看起来像:

@RestController
@RequestMapping("/nutrition/api/")
class NutritionixApiController {

  private ProductFacadeImpl productFacadeImpl;

  public NutritionixApiController(
      ProductFacadeImpl productFacadeImpl) {
    this.productFacadeImpl = productFacadeImpl;
  }

  @GetMapping("/productDetails")
  public ResponseEntity<Set<RecipeIngredient>> productsDetails(@RequestParam String query) {
  //logic here
  }
}

我试过把网址列入白名单 "/nutrition/api/**" in方法 configure(WebSecurity web) 通过添加:

web.ignoring().antMatchers(GET, "/nutrition/api/**");

web.ignoring().antMatchers(GET, "/**");

但没有理想的效果。我有点不明白为什么 ignoring.antMatchers() 没有工作,所以我将非常感谢关于如何解决上述问题的建议。

暂无答案!

目前还没有任何答案,快来回答吧!

相关问题