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

基于JWT生成Principal并设置到SecurityContext的实现咨询

问题分析

你之前的思路存在核心误区:SecurityContextHolder的上下文是线程绑定的,/principal接口中设置的认证信息仅对当前请求线程有效。请求处理完成后线程销毁,上下文也会被清空,后续的/books请求属于新线程,自然没有认证信息,所以返回403。

正确的做法是通过Spring Security的请求过滤器,在每个请求到达时自动解析JWT Token并注入认证信息,而不是手动通过接口设置。

解决方案步骤

1. 编写JWT工具类(解析Token、转换权限)

基于你生成Token时使用的JJWT库,实现Token解析和权限转换逻辑:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.List;
import java.util.stream.Collectors;

public class JwtUtils {
    // 和生成Token时一致的密钥
    private static final String SECRET_KEY = "mysecret";

    // 解析Token并获取Claims
    public static Claims parseToken(String token) {
        // 移除Bearer前缀(如果请求头用Bearer格式)
        String cleanToken = token.startsWith("Bearer ") ? token.substring(7) : token;
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(cleanToken)
                .getBody();
    }

    // 将Token中的securityGroups转换为Spring Security的权限对象
    public static List<SimpleGrantedAuthority> getAuthorities(Claims claims) {
        List<String> groups = (List<String>) claims.get("securityGroups");
        // 加上ROLE_前缀适配Spring Security默认的权限规则
        return groups.stream()
                .map(group -> new SimpleGrantedAuthority("ROLE_" + group))
                .collect(Collectors.toList());
    }
}

2. 实现JWT认证过滤器

自定义过滤器,在每个请求到达时自动解析Token并设置认证信息:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import io.jsonwebtoken.Claims;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 从请求头获取Token(约定用Authorization: Bearer <token>格式)
        String authHeader = request.getHeader("Authorization");
        
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            try {
                Claims claims = JwtUtils.parseToken(authHeader);
                String username = claims.getSubject();
                // 获取用户权限
                var authorities = JwtUtils.getAuthorities(claims);
                
                // 创建认证对象并注入上下文
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        username, null, authorities
                );
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } catch (Exception e) {
                // Token无效时清空上下文,返回401
                SecurityContextHolder.clearContext();
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的JWT Token");
                return;
            }
        }
        
        // 继续执行后续过滤链
        filterChain.doFilter(request, response);
    }
}

3. 修改Spring Security配置

将自定义过滤器加入安全过滤链,替换默认的认证逻辑:

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConf extends WebSecurityConfigurerAdapter {

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/principal").permitAll() // 保留该接口(可选,可后续删除)
                .anyRequest().authenticated()
                .and()
                // 在默认的用户名密码过滤器之前注入JWT过滤器
                .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

4. 权限控制(可选)

如果需要对特定接口做权限限制,比如仅允许group1访问/books,可以修改配置:

// 在SecurityConf的configure方法中添加
.antMatchers("/books").hasRole("group1")

或者在接口上使用注解:

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {
    @GetMapping("/books")
    @PreAuthorize("hasRole('group1')")
    public List<String> getBooks() {
        return List.of("book1", "book2");
    }
}
后续使用说明
  1. 前端在获取到JWT Token后,后续所有请求都需要在请求头中携带:Authorization: Bearer <你的Token>
  2. 原/principal接口可以保留用来测试Token解析,但不再需要手动设置SecurityContext,过滤器会自动处理所有请求的认证

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 09:05:28