You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Kotlin REST接口JWT授权拦截异常求助

Spring Boot + Kotlin + JWT权限控制问题:POST请求/api/employees被拦截分析

用Kotlin开发Spring Boot REST服务,通过Spring Security与JWT实现权限控制,已完成SecurityFilterChain配置、JwtTokenService、JwtAuthenticationFilter及JwtAuthorizationFilter的编写。按预期设置SecurityContextHolder.getContext().authentication后请求应被授权,但POST请求/api/employees仍被拦截。开启Spring Security TRACE日志后,显示AuthorizationFilter处理时SecurityContextHolder已有认证信息,但请求仍被阻止,求分析原因。

相关代码及日志如下:

1. SecurityFilterChain配置

@Bean
fun filterChain(
    http: HttpSecurity,
    authenticationManager: UserAuthenticationManager
): SecurityFilterChain
{
    return http
        .csrf()
            .disable()
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().authorizeHttpRequests()
            .requestMatchers(HttpMethod.GET, "/api/**")
                .permitAll()
            .anyRequest()
                .authenticated()
        .and()
            .addFilterAfter(
                JwtAuthenticationFilter(jwtTokenService, authenticationManager, securityProperties),
                UsernamePasswordAuthenticationFilter::class.java)
            .addFilterAfter(
                JwtAuthorizationFilter(jwtTokenService, authenticationManager, securityProperties),
                JwtAuthenticationFilter::class.java)
        .build()
}

2. JwtTokenService实现

@Component
class JwtTokenService(
    @Autowired val securityProperties: SecurityProperties,
    @Autowired val userService: UserService
)
{
    private var key: Key? = null

    @PostConstruct
    fun init() {
        key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(securityProperties.secret))
    }

    fun generateToken(user: UserDetails): String
    {
        val now = Instant.now()
        val expiryDate = now.plusSeconds(securityProperties.expiration)

        return Jwts.builder()
            .setSubject(user.username)
            .setIssuedAt(Date.from(now))
            .setExpiration(Date.from(expiryDate))
            .signWith(key, SignatureAlgorithm.HS512)
            .compact()
    }

    fun getAuthentication(token: String): Authentication?
    {
        return try {
            val claims = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token.replace(securityProperties.tokenPrefix, ""))
            val user = userService.loadUserByUsername(claims.body.subject)
            UsernamePasswordAuthenticationToken(user, token, user.authorities)
        } catch (e: Exception) {
            return null
        }
    }
}

3. JwtAuthenticationFilter(登录认证过滤器)

注:仅当continueChainBeforeSuccessfulAuthentication设为false时返回200,设为true则请求失败

class JwtAuthenticationFilter(
    private val jwtTokenService: JwtTokenService,
    private val authenticationManager: UserAuthenticationManager,
    private val securityProperties: SecurityProperties
): AbstractAuthenticationProcessingFilter(
    AntPathRequestMatcher("/api/login", "POST"), authenticationManager)
{
    override fun attemptAuthentication(
        request: HttpServletRequest,
        response: HttpServletResponse
    ): Authentication
    {
        val loginRequest = ObjectMapper()
            .readValue(request.inputStream, LoginRequest::class.java)
        val authenticationToken = UsernamePasswordAuthenticationToken(
            loginRequest.username, loginRequest.password)
        return authenticationManager.authenticate(authenticationToken)
    }

    override fun successfulAuthentication(
        request: HttpServletRequest,
        response: HttpServletResponse,
        chain: FilterChain,
        authResult: Authentication
    ) {
        val user = authResult.principal as User
        val token = jwtTokenService.generateToken(user)

        response.addHeader(
            securityProperties.headerString,
            securityProperties.tokenPrefix + token)
    }
}

4. JwtAuthorizationFilter(授权过滤器)

class JwtAuthorizationFilter(
    private val jwtTokenService: JwtTokenService,
    authenticationManager: UserAuthenticationManager,
    private val securityProperties: SecurityProperties
) : BasicAuthenticationFilter(authenticationManager)
{
    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    )
    {
        val header = request.getHeader(securityProperties.headerString)
        if (header == null || !header.startsWith(securityProperties.tokenPrefix)) {
            filterChain.doFilter(request, response)
            return
        }    

        jwtTokenService.getAuthentication(header)?.let { authentication ->
            SecurityContextHolder.getContext().authentication = authentication
        }

        // continue with authenticated user
        filterChain.doFilter(request, response)
    }
}

5. POST /api/employees请求的TRACE日志

Securing POST /api/employees/
  Invoking DisableEncodeUrlFilter (1/13)
  Invoking WebAsyncManagerIntegrationFilter (2/13)
  Invoking SecurityContextHolderFilter (3/13)
  Invoking HeaderWriterFilter (4/13)
  Invoking LogoutFilter (5/13)
  Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
  Invoking JwtAuthenticationFilter (6/13)
  Invoking JwtAuthorizationFilter (7/13)
  Created SecurityContextImpl [Null authentication]
  Invoking RequestCacheAwareFilter (8/13)
  Invoking SecurityContextHolderAwareRequestFilter (9/13)
  Invoking AnonymousAuthenticationFilter (10/13)
  Invoking SessionManagementFilter (11/13)
  Did not set SecurityContextHolder since already authenticated UsernamePasswordAuthenticationToken [Principal=at.oebb.tools.routing.user.User@7a529298, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[at.oebb.tools.routing.user.User$$Lambda$788/0x00000008014bd130@57cd77e2]]
  Preparing session with ChangeSessionIdAuthenticationStrategy (1/1)
  Invoking ExceptionTranslationFilter (12/13)
  Invoking AuthorizationFilter (13/13)
  Authorizing SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@61cc275c]
  Checking authorization on SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@61cc275c] using org.springframework.security.authorization.AuthenticatedAuthorizationManager@332aabc6
  Secured POST /api/employees/

内容的提问来源于stack exchange,提问作者ChrLipp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 04:57:03