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
相关产品推荐
相关产品推荐

