Search code examples
spring-bootspring-securityjwt

Spring boot 3 and Spring security Pre Authorization


I usually try to avoid using spring auth because it usually injects to many defaults that are extremely difficult to disable. But now I have a need to use JWT tokens. So I have created a security config but there is a Pre Authentication failure occurring that I can't firgure out how to disable: Pre-authenticated entry point called. Rejecting access Can anyone share with me how to disable the pre auth in a boot 3 app? Below is my current config

@Bean
@Throws(Exception::class)
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
    return http.csrf { it.disable() }
        .authorizeHttpRequests { it.anyRequest().authenticated() }
        .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
        .authenticationProvider(authenticationProvider())
        .build()
}

@Bean
fun authenticationProvider(): AuthenticationProvider {
    val authenticationProvider = JwtAuthenticationProvider(jwtDecoder())
    return authenticationProvider
}

@Bean
fun jwtDecoder(): JwtDecoder {
    return NimbusJwtDecoder.withPublicKey(rsaProperties.publicKey).build()
}

@Bean
fun jwtEncoder(): JwtEncoder {
    val jwk: JWK =
        RSAKey.Builder(rsaProperties.publicKey).privateKey(rsaProperties.privateKey).build()

    val jwks: JWKSource<SecurityContext> = ImmutableJWKSet(JWKSet(jwk))
    return NimbusJwtEncoder(jwks)
}

Debug Logs for Spring Security Web:

2024-02-14T13:58:20.238-08:00 DEBUG 5308 --- [tp1468039653-29] o.s.security.web.FilterChainProxy: Securing POST /v1/ds/123456789/register
2024-02-14T13:58:20.239-08:00 DEBUG 5308 --- [tp1468039653-29] o.s.s.w.a.AnonymousAuthenticationFilter: Set SecurityContextHolder to anonymous SecurityContext
2024-02-14T13:58:20.239-08:00 DEBUG 5308 --- [tp1468039653-29] o.s.s.w.a.Http403ForbiddenEntryPoint     : Pre-authenticated entry point called. Rejecting access
2024-02-14T13:58:20.243-08:00 DEBUG 5308 --- [tp1468039653-29] o.s.security.web.FilterChainProxy        : Securing POST /error
2024-02-14T13:58:20.244-08:00 DEBUG 5308 --- [tp1468039653-29] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-02-14T13:58:20.244-08:00 DEBUG 5308 --- [tp1468039653-29] o.s.s.w.a.Http403ForbiddenEntryPoint     : Pre-authenticated entry point called. Rejecting access

Solution

  • So the problem was that I believed that because the AuthenticationProvider was a JwtAuthenticationProvider, the guts of the jwt security filtering would be "provided" by that authentication provider, it is not. I updated my security config to have a jwtFilter along with the auth provider and now it is working. To resolve some circular references the encoder/decode beans from the question above were moved to a separate bean configuration class.

    @Configuration
    @EnableWebSecurity
    class SecurityConfig(
      private val jwtDecoder: JwtDecoder,
      private val jwtfilter: JwtAuthFilter
    ) {
    
      @Bean
      @Throws(Exception::class)
      fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        return http.csrf { it.disable() }
            .authorizeHttpRequests { it.anyRequest().authenticated() }
            .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
            .authenticationProvider(authenticationProvider())
            .addFilterBefore(jwtfilter, UsernamePasswordAuthenticationFilter::class.java)
            .build()
      }
    
      @Bean
      fun authenticationProvider(): AuthenticationProvider {
        val authenticationProvider = JwtAuthenticationProvider(jwtDecoder)
        return authenticationProvider
      }
    }
    

    Here is the filter as well for those who want it, the jwtService referenced in the filter simply uses the NimbusJwtDecoder and NimbusJwtEncoder to do all the heavy lifting:

    @Component
    class JwtAuthFilter(private val jwtService: JwtService) : OncePerRequestFilter() {
    val log: Logger = LoggerFactory.getLogger(javaClass)
    
      override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain) {
        val authHeader: String = request.getHeader("Authorization") ?: throw InvalidJwtException("No JWT Header provided")
        var token: String? = null
        var cachingRequest: ContentCachingRequestWrapper? = null
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            token = authHeader.removePrefix("Bearer").trim()
        }
        val contentType = request.contentType
        if (contentType == null || !contentType.contains("multipart/form-data")) {
            // Wrap the request, so we can read the payload multiple times
            cachingRequest = ContentCachingRequestWrapper(request)
        }
        val payload = cachingRequest?.reader?.readText() ?: ""
    
        if (SecurityContextHolder.getContext().authentication == null) {
            log.trace("Validating JWT token")
            jwtService.validateToken(token!!, payload)?.let {
                val authenticationToken = JwtAuthenticationToken(it, listOf(SimpleGrantedAuthority("ROLE_USER")))
                SecurityContextHolder.getContext().authentication = authenticationToken
            } ?: throw InvalidJwtException("Invalid JWT token provided")
        }
        filterChain.doFilter(request, response)
      }
    }