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