简体   繁体   中英

Spring Security returning 200 instead of 401 as stated by HttpWebHandlerAdapter

Trying to figure out if I just discovered a bug in Spring Security, this is against the latest 2.4.5 release. The HttpWebHandlerAdapter is stating that it is returning 401 in the logs, yet the response in Postman is 200. Below are relevant spring security configuration/handlers, etc.

Spring Security config

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity(proxyTargetClass = true)
@RequiredArgsConstructor
public class SecurityConfig {
  private final JwtAuthenticationConverter jwtAuthenticationConverter;
  private final UsersRepository usersRepository;
  private final UserRolesRepository userRolesRepository;
  private final RoleScopesRepository roleScopesRepository;
  private final JwtUtil jwtUtil;

  private static Map<HttpMethod, String[]> AUTH_WHITELIST =
      Map.of(
          // Public auth endpoints
          HttpMethod.PUT, new String[] {"/v1/auth/login"},
          HttpMethod.POST, new String[] {"/v1/auth/register"},
          HttpMethod.GET,
              new String[] {
                // Actuator
                "/actuator",
                "/actuator/health*",
                "/actuator/info",
                // SpringFox/OpenAPI
                "/v3/api-docs/**",
                "/swagger-ui/**",
                "/swagger-resources/**",
                "/webjars/swagger-ui/**",
                // Public API endpoints
                "/v1/posts/*/comments/*",
                "/v1/posts/*/comments",
                "/v1/posts/*",
                "/v1/posts"
              });

  @Bean
  public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    // Build path/verb matchers
    Set<ServerWebExchangeMatcher> matchers = new HashSet<>();
    AUTH_WHITELIST.forEach(
        (method, paths) -> matchers.add(ServerWebExchangeMatchers.pathMatchers(method, paths)));
    ServerWebExchangeMatcher[] matchersArray = matchers.toArray(new ServerWebExchangeMatcher[0]);

    return http.addFilterAt(
            getAuthenticationWebFilter(matchersArray), SecurityWebFiltersOrder.AUTHENTICATION)
        .authorizeExchange()
        .matchers(matchersArray)
        .permitAll()
        .anyExchange()
        .authenticated()
        .and()
        .formLogin()
        .and()
        .csrf()
        .disable()
        .cors()
        .configurationSource(createCorsConfigSource())
        .and()
        .formLogin()
        .disable()
        .httpBasic()
        .disable()
        .logout()
        .disable()
        .build();
  }

  public CorsConfigurationSource createCorsConfigSource() {
    org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource source =
        new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.addAllowedOrigin("http://localhost:3000");
    config.setAllowedMethods(List.of("OPTIONS", "GET", "PUT", "POST", "DELETE"));
    source.registerCorsConfiguration("/**", config);
    return source;
  }

  private AuthenticationWebFilter getAuthenticationWebFilter(
      ServerWebExchangeMatcher[] matchersArray) {
    // Create web filter with custom user details service/authentication manager
    AuthenticationWebFilter authenticationWebFilter =
        new AuthenticationWebFilter(new AuthenticationManager(customUserDetailsService()));
    // Set custom JWT authentication converter
    authenticationWebFilter.setServerAuthenticationConverter(jwtAuthenticationConverter);
    // Negate whitelist to set paths with required authentication
    NegatedServerWebExchangeMatcher negateWhiteList =
        new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.matchers(matchersArray));
    authenticationWebFilter.setRequiresAuthenticationMatcher(negateWhiteList);
    // Add failure handler
    authenticationWebFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler());
    return authenticationWebFilter;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  @Primary
  public UserDetailsService customUserDetailsService() {
    return new UserDetailsService(
        new UserService(
            usersRepository,
            userRolesRepository,
            roleScopesRepository,
            passwordEncoder(),
            jwtUtil));
  }
}

Failure handler

@Slf4j
public class AuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
  @Override
  public Mono<Void> onAuthenticationFailure(
      WebFilterExchange webFilterExchange, AuthenticationException exception) {
    log.warn(exception.getMessage());
    ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    response.getHeaders().addIfAbsent(HttpHeaders.LOCATION, "/");
    response.setComplete();
    return Mono.error(exception);
  }
}

JWT auth converter.

@Slf4j
@RequiredArgsConstructor
@Component
public class JwtAuthenticationConverter implements ServerAuthenticationConverter {
  private final JwtUtil jwtUtil;

  private Mono<String> extractJwtFromAuthorizationHeader(ServerWebExchange exchange) {
    return Mono.justOrEmpty(exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION))
        // Remove empty headers/headers with empty string as value
        .filter(
            header ->
                !header.isEmpty()
                    && StringUtils.hasText(header.get(0))
                    && header.get(0).contains("Bearer"))
        .map(header -> header.get(0).replaceAll(AuthConstants.BEARER_PREFIX_REGEX, ""))
        .switchIfEmpty(Mono.error(new InvalidJwtException("Invalid bearer token")));
  }

  @Override
  public Mono<Authentication> convert(ServerWebExchange exchange) {
    return Mono.justOrEmpty(exchange)
        .flatMap(this::extractJwtFromAuthorizationHeader)
        .map(jwtUtil::getAuthenticationFromJwt);
  }
}

Authentication manager

public class AuthenticationManager extends UserDetailsRepositoryReactiveAuthenticationManager {

  public AuthenticationManager(ReactiveUserDetailsService userDetailsService) {
    super(userDetailsService);
  }

  @Override
  public Mono<Authentication> authenticate(Authentication authentication) {
    return authentication.isAuthenticated()
        ? Mono.just(authentication)
        : super.authenticate(authentication);
  }
}

Relevant logs

2021-05-05 15:41:18.981 DEBUG 1984531 --- [or-http-epoll-3] o.s.w.s.h.ResponseStatusExceptionHandler : [82168f6e-13] Resolved [InvalidJwtException: Unsupported JWT token: The parsed JWT indicates it was signed with the HS512 signature algorithm, but the specified signing key of type sun.security.rsa.RSAPublicKeyImpl may not be used to validate HS512 signatures.  Because the specified signing key reflects a specific and expected algorithm, and the JWT does not reflect this algorithm, it is likely that the JWT was not expected and therefore should not be trusted.  Another possibility is that the parser was configured with the incorrect signing key, but this cannot be assumed for security reasons.] for HTTP POST /v1/posts
2021-05-05 15:41:18.981 DEBUG 1984531 --- [or-http-epoll-3] o.s.w.s.adapter.HttpWebHandlerAdapter    : [82168f6e-13] Completed 401 UNAUTHORIZED

Not a bug, setComplete() on the failure handler was the offending line. No more 200s while logs say 401.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM