【问题标题】:Migrating Spring Security AbstractAuthenticationProcessingFilter to WebFlux将 Spring Security AbstractAuthenticationProcessingFilter 迁移到 WebFlux
【发布时间】:2020-04-06 05:55:22
【问题描述】:

我正在更新一个旧应用程序以使用 WebFlux,但是在使用 Spring Security 处理 JWT 验证时我有点迷失了。

现有代码(适用于标准 Spring Web)如下所示:

(验证 Firebase 令牌)

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));
  }

但是,当切换到 WebFlux 时,我们会丢失 HttpServletRequestHttpServletResponse。有一个 GitHub 问题,表明有一种替代方法/修复 https://github.com/spring-projects/spring-security/issues/5328 但是通过它,我无法确定实际更改了什么以使其工作。

Spring Security 文档虽然很棒,但并没有真正解释如何处理用例。

关于如何进行的任何提示?

【问题讨论】:

    标签: java spring spring-security spring-webflux


    【解决方案1】:

    对于上一个答案,可以补充说这种方法也可以正常工作:

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

    而且这个没有提供关于不必要使用阻塞方法的警告(如get()

    【讨论】:

      【解决方案2】:

      终于到了:

      首先需要像之前一样使用自定义过滤器更新过滤器链

      @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;
        }
      }
      

      该过程的主要工作是FirebaseAuthenticationConverter,我在其中针对 Firebase 验证传入的 JWT,并针对它执行一些标准逻辑。

      @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);
        }
      }
      

      【讨论】:

      • 作为一种改进,如果您想在返回Mono的方法中抛出错误,请改用Mono.error()
      • 最重要的是,据我了解,您可以简单地摆脱 Spring Security,因为现在它只起到双重路径匹配和过滤器双重包装的作用。将过滤器定义为bean,就是这样。
      猜你喜欢
      • 2017-05-30
      • 1970-01-01
      • 1970-01-01
      • 2015-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-07
      相关资源
      最近更新 更多