2

I configured Spring Webflux with JWT. Created custom ServerSecurityContextRepository and added it into SecurityWebFilterChain. But when I get SecurityContext while incoming requests, Security context returns always null.

The following codes are my configurations:

SecurityContextRepository.class

@Component
public class SecurityContextRepository implements ServerSecurityContextRepository {

    private static final Logger LOGGER = LoggerFactory.getLogger(SecurityContextRepository.class);
    private static final Pattern PATTERN = Pattern.compile("Bearer (.+)");

    private final JWTVerifier verifier;
    private final UserRepository userRepository;

    @SuppressFBWarnings("EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS")
    public SecurityContextRepository(
            SignatureAlgorithmProvider algorithmProvider, UserRepository userRepository) {
        this.userRepository = requireNonNull(userRepository);

        try {
            verifier = JWT.require(algorithmProvider.algorithm()).withIssuer(JwtTokenGenerator.ISSUER).build();
        } catch (Exception e) {
            throw new BeanInitializationException("Could not initialize JWT verifier", e);
        }
    }

    @Override
    public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) {
        var token = extractToken(serverWebExchange.getRequest());

        if (token == null) {
            return Mono.empty();
        } else {
            try {

                var jwt = verifier.verify(token);
                var userId = UUID.fromString(jwt.getSubject());
                var userOptional = userRepository.fetchOneById(userId);

                if (userOptional.isPresent()) {
                    var user = userOptional.get();
                    return Mono.just(new SecurityContextImpl(new JwtAuthenticationToken(securityUser, roles(user.roles()))));
                }
                return Mono.empty();
            } catch (JWTDecodeException | IllegalArgumentException e) {
                LOGGER.warn("Bearer token cannot be decoded as JWT: {}", token, e);
                return Mono.error(new UnauthorizedException("Bearer token cannot be decoded as JWT.", e));
            } catch (SignatureVerificationException e) {
                LOGGER.warn("Received access token with an invalid signature: {}", token, e);
                return Mono.error(
                        new UnauthorizedException("Received access token with an invalid signature.", e));
            } catch (TokenExpiredException e) {
                LOGGER.warn("JWT token expired.", e);
                return Mono.error(new UnauthorizedException("JWT token expired.", e));
            } catch (Exception e) {
                LOGGER.warn("Exception verifying JWT: {}", token, e);
                return Mono.error(new UnauthorizedException("Exception verifying JWT.", e));
            }
        }
    }
}

SecurityConfiguration.class

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

    private final SecurityContextRepository securityContextRepository;

    @Autowired
    public SecurityConfiguration(SecurityContextRepository securityContextRepository) {
        this.securityContextRepository = requireNonNull(securityContextRepository);
    }

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

    @SuppressFBWarnings(value = "SPRING_CSRF_PROTECTION_DISABLED")
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
                // Disable default security.
        return http.httpBasic()
                    .disable()
                    .formLogin()
                    .disable()
                    .csrf()
                    .disable()
                    .logout()
                    .disable()
                    .cors()
                    .and()
                    // Add custom security.
                    .securityContextRepository(securityContextRepository)
                    .addFilterAt(
                            new SecurityContextServerWebExchangeWebFilter(),
                            SecurityWebFiltersOrder.SECURITY_CONTEXT_SERVER_WEB_EXCHANGE)
                    .authorizeExchange()
                    .pathMatchers("/api/authentication/v1/*", "/status", "/info", "/health", "/docs/**")
                    .permitAll()
                    .pathMatchers(HttpMethod.GET, "/api/locations/v1/**")
                    .permitAll()
                    .anyExchange()
                    .authenticated()
                    .and()
                    .exceptionHandling()
                    .and()
                    .build();
    }
}

Jwt version: com.auth0:java-jwt:3.10.3 SpringBoot version: 2.3.2:RELEASE

Toerktumlare
  • 12,548
  • 3
  • 35
  • 54
  • Spring Security uses the current threads thread local to store the security context. This collides with the philosophy of Project reactor, in which a thread might handle many different requests and therefore have many different security context to handle [see here](https://www.naturalprogrammer.com/blog/16393/spring-security-get-current-user-programmatically). I assume that your issue is that you are trying to access the security context stored in thread local which for WebFlux correctly is null. – Bernard Aug 04 '20 at 08:28
  • In my case I use ReactiveSecurityContextHolder instead SecurityContextHolder to fetch current authentication. But both SecurityContex and ReactiveSecurity context are null. I knew that by default ServerSecurityContextRepository is used in AuthenticationWebFilter. But it is an instance is NoOpServerSecurityContextRepository. The NoOpServerSecurityContextRepository does not store authentication in context. I tried to authenticate the request using a custom token and put it into SecurityContext. But it also did not help. – Nasibulloh Yandashev Aug 04 '20 at 09:44
  • is there any particular reason as to why you are not using the built in JWT support and customizing it instead of trying to build a complete own custom login? https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver – Toerktumlare Aug 04 '20 at 19:08
  • @ThomasAndolf I did not want to use OAuth2.0 specifications. I dont need to use authorization/resource server. I just used JWT support without OAuth2.0 – Nasibulloh Yandashev Aug 07 '20 at 11:12
  • Thats is not following any standard and is bad practice. Good luck – Toerktumlare Aug 07 '20 at 13:57

0 Answers0