JwtAuthFilter.kt

package org.openapitools.security

import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.openapitools.repository.TokenBlocklistRepository
import org.openapitools.repository.UserRepository
import org.slf4j.LoggerFactory
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.User
import org.springframework.web.filter.OncePerRequestFilter

class JwtAuthFilter(
	private val jwtUtils: JwtUtils,
	private val userRepository: UserRepository,
	private val tokenBlocklist: TokenBlocklistRepository,
) : OncePerRequestFilter() {
	private val log = LoggerFactory.getLogger(javaClass)

	override fun doFilterInternal(
		request: HttpServletRequest,
		response: HttpServletResponse,
		filterChain: FilterChain,
	) {
		val header = request.getHeader("Authorization")
		if (header != null && header.startsWith("Bearer ")) {
			val token = header.removePrefix("Bearer ")
			when {
				!jwtUtils.validateToken(token) -> {
					log.warn("Rejected invalid JWT [path={}]", request.requestURI)
				}

				tokenBlocklist.existsById(jwtUtils.tokenHash(token)) -> {
					log.warn("Rejected invalidated JWT [path={}]", request.requestURI)
				}

				else -> {
					val userId = jwtUtils.getUserIdFromToken(token)
					userRepository.findById(userId).ifPresent { entity ->
						val userDetails =
							User
								.withUsername(entity.username)
								.password(entity.password)
								.roles("USER")
								.build()
						// Store the raw token as credentials so logout can retrieve and blocklist it.
						val auth = UsernamePasswordAuthenticationToken(userDetails, token, userDetails.authorities)
						SecurityContextHolder.getContext().authentication = auth
					}
				}
			}
		}
		filterChain.doFilter(request, response)
	}

	// set to false to avoid controller 500s being masked as 401
	override fun shouldNotFilterErrorDispatch(): Boolean = false
}