简体   繁体   中英

Migrating Spring Security AbstractAuthenticationProcessingFilter to WebFlux

I'm updating an old application to use WebFlux but I've gotten a bit lost when it comes to handling JWT validation with Spring Security.

The existing code (which works with standard Spring Web) looks like:

(Validating a Firebase Token)

public class FirebaseAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter {

  private static final String TOKEN_HEADER = "X-Firebase-Auth";

  public FirebaseAuthenticationTokenFilter() {
    super("/v1/**");
  }

  @Override
  public Authentication attemptAuthentication(
      final HttpServletRequest request, final HttpServletResponse response) {

    for (final Enumeration<?> e = request.getHeaderNames(); e.hasMoreElements(); ) {
      final String nextHeaderName = (String) e.nextElement();
      final String headerValue = request.getHeader(nextHeaderName);
    }

    final String authToken = request.getHeader(TOKEN_HEADER);
    if (Strings.isNullOrEmpty(authToken)) {
      throw new RuntimeException("Invaild auth token");
    }

    return getAuthenticationManager().authenticate(new FirebaseAuthenticationToken(authToken));
  }

However when switching to WebFlux we lose HttpServletRequest and HttpServletResponse . There is a GitHub issue which suggests there is an alternative method/fix https://github.com/spring-projects/spring-security/issues/5328 however following it through I'm not able to identify what was actually changed to make this work.

The Spring Security docs while great, don't really explain how to handle the use-case.

Any tips on how to proceed?

Got there in the end:

First need to update the filter chain with a custom filter just like before

@Configuration
public class SecurityConfig {

  private final FirebaseAuth firebaseAuth;

  public SecurityConfig(final FirebaseAuth firebaseAuth) {

    this.firebaseAuth = firebaseAuth;
  }

  @Bean
  public SecurityWebFilterChain springSecurityFilterChain(final ServerHttpSecurity http) {

    http.authorizeExchange()
        .and()
        .authorizeExchange()
        .pathMatchers("/v1/**")
        .authenticated()
        .and()
        .addFilterAt(firebaseAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
        .csrf()
        .disable();

    return http.build();
  }

  private AuthenticationWebFilter firebaseAuthenticationFilter() {
    final AuthenticationWebFilter webFilter =
        new AuthenticationWebFilter(new BearerTokenReactiveAuthenticationManager());

    webFilter.setServerAuthenticationConverter(new FirebaseAuthenticationConverter(firebaseAuth));
    webFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers("/v1/**"));

    return webFilter;
  }
}

The main workhorse of the process is FirebaseAuthenticationConverter where I validate the incoming JWT against Firebase, and perform some standard logic against it.

@Slf4j
@Component
@RequiredArgsConstructor
public class FirebaseAuthenticationConverter implements ServerAuthenticationConverter {

  private static final String BEARER = "Bearer ";

  private static final Predicate<String> matchBearerLength =
      authValue -> authValue.length() > BEARER.length();

  private static final Function<String, Mono<String>> isolateBearerValue =
      authValue -> Mono.justOrEmpty(authValue.substring(BEARER.length()));

  private final FirebaseAuth firebaseAuth;

  private Mono<FirebaseToken> verifyToken(final String unverifiedToken) {
    try {
      final ApiFuture<FirebaseToken> task = firebaseAuth.verifyIdTokenAsync(unverifiedToken);

      return Mono.justOrEmpty(task.get());
    } catch (final Exception e) {
      throw new SessionAuthenticationException(e.getMessage());
    }
  }

  private Mono<FirebaseUserDetails> buildUserDetails(final FirebaseToken firebaseToken) {
    return Mono.just(
        FirebaseUserDetails.builder()
            .email(firebaseToken.getEmail())
            .picture(firebaseToken.getPicture())
            .userId(firebaseToken.getUid())
            .username(firebaseToken.getName())
            .build());
  }

  private Mono<Authentication> create(final FirebaseUserDetails userDetails) {
    return Mono.justOrEmpty(
        new UsernamePasswordAuthenticationToken(
            userDetails.getEmail(), null, userDetails.getAuthorities()));
  }

  @Override
  public Mono<Authentication> convert(final ServerWebExchange exchange) {
    return Mono.justOrEmpty(exchange)
        .flatMap(AuthorizationHeaderPayload::extract)
        .filter(matchBearerLength)
        .flatMap(isolateBearerValue)
        .flatMap(this::verifyToken)
        .flatMap(this::buildUserDetails)
        .flatMap(this::create);
  }
}

To the previous answer there could be added that this method also works fine:

private Mono<FirebaseToken> verifyToken(final String unverifiedToken) {
        try {
            return Mono.just(FirebaseAuth.getInstance().verifyIdToken(unverifiedToken));
        } catch (final Exception e) {
            throw new SessionAuthenticationException(e.getMessage());
        }
    }

And this one does not provid warnings regarding unnecessary use of blocking methods (like get() )

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