🔐 Spring Security + JWT 认证体系分析
1. JwtAuthenticationFilter.java - JWT认证过滤器
🎯 作用
这是整个JWT认证的核心过滤器,负责从HTTP请求中提取和验证JWT token。
🔍 工作原理
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        // 1. 从请求头获取Token
        String tokenHeader = request.getHeader(TokenUtil.TOKEN_HEADER);
        
        // 2. 如果没有Token,直接放行(可能是公开接口)
        if (tokenHeader == null) {
            chain.doFilter(request, response);
            return;
        } 
        
        // 3. 有Token则解析并设置到Spring Security上下文
        else {
            UsernamePasswordAuthenticationToken authentication = getAuthentication(tokenHeader);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        
        super.doFilterInternal(request, response, chain);
    }
}
📝 详细流程
HTTP请求 → 检查Header中的Authorization → 解析JWT Token → 设置Security上下文 → 放行请求
🔧 关键方法分析
private UsernamePasswordAuthenticationToken getAuthentication(String token) {
    final Long userId = TokenUtil.getUserId(token);        // 从token解析用户ID
    String permissionList = TokenUtil.getPermissionList(token); // 从token解析权限列表
    
    if (userId != null) {
        // 创建认证对象:用户ID + 权限列表
        return new UsernamePasswordAuthenticationToken(
            userId, 
            null, 
            AuthorityUtils.commaSeparatedStringToAuthorityList(permissionList)
        );
    }
    return null;
}
2. JwtAuthenticationEntryPoint.java - 认证入口点
🎯 作用
当用户没有提供有效token或token过期时,这个类负责处理认证失败的情况。
🔍 工作原理
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, 
                        AuthenticationException e) throws IOException, ServletException {
        
        log.error("用户访问资源没有携带正确的token,msg:{}", e.getMessage());
        
        // 返回JSON格式的错误响应
        final String json = JSON.toJSONString(new JsonResult(30001, "用户访问资源没有携带正确的token", e.getMessage()));
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(json);
    }
}
📝 触发场景
- ❌ 请求头中没有Authorization字段
- ❌ Token格式错误
- ❌ Token已过期
- ❌ Token签名验证失败
🎨 响应格式
{
    "code": 30001,
    "message": "用户访问资源没有携带正确的token",
    "data": "具体错误信息"
}
3. JwtAccessDeniedHandler.java - 访问拒绝处理器
🎯 作用
当用户已经认证成功但没有访问某个资源的权限时,这个类负责处理授权失败的情况。
🔍 工作原理
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, 
                      AccessDeniedException e) throws IOException, ServletException {
        
        log.error("用户访问没有授权资源,msg:{}", e.getMessage());
        
        // 返回JSON格式的权限不足响应
        final String json = JSON.toJSONString(new JsonResult<>(30002, "用户访问没有授权资源", e.getMessage()));
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(json);
    }
}
📝 触发场景
- ✅ 用户已登录(token有效)
- ❌ 但访问了需要更高权限的接口
- ❌ 例如:普通用户访问管理员接口
🎨 响应格式
{
    "code": 30002,
    "message": "用户访问没有授权资源",
    "data": "具体权限错误信息"
}
4. PrincipalUserArgumentResolver.java - 用户参数解析器
🎯 作用
这是一个方法参数解析器,让Controller方法可以直接通过参数获取当前登录用户信息,而不需要手动从SecurityContext中提取。
🔍 工作原理
public class PrincipalUserArgumentResolver implements HandlerMethodArgumentResolver {
    
    // 1. 判断是否支持解析该参数类型
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType().equals(MyUser.class);
    }
    
    // 2. 解析参数,返回当前登录用户
    @Override
    public Object resolveArgument(...) throws Exception {
        // 从Security上下文获取认证信息
        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return null;
        }
        
        // 提取用户ID并查询用户详情
        final Long usId = (Long) authentication.getPrincipal();
        final MyUser res = userService.getMyUserById(usId);
        return res;
    }
}
📝 使用示例
@RestController
public class BlogController {
    
    // 🎯 直接通过参数获取当前登录用户,无需手动解析
    @PostMapping("/api/blogs")
    public Result<Blog> createBlog(@RequestBody Blog blog, MyUser currentUser) {
        // currentUser 会自动注入当前登录用户信息
        blog.setAuthor(currentUser);
        return blogService.save(blog);
    }
}
🔄 完整的请求处理流程
请求流程图
HTTP请求
    ↓
1. JwtAuthenticationFilter (认证过滤器)
    ├─ 提取Token
    ├─ 验证Token
    └─ 设置Security上下文
    ↓
2. Spring Security权限检查
    ├─ 检查是否需要认证
    └─ 检查用户权限
    ↓
3. Controller方法调用
    ├─ PrincipalUserArgumentResolver解析用户参数
    └─ 执行业务逻辑
    ↓
4. 异常处理(如果有)
    ├─ JwtAuthenticationEntryPoint (认证失败)
    └─ JwtAccessDeniedHandler (权限不足)
详细处理步骤
🟢 成功流程
// 1. 用户发送请求
GET /api/blogs HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
// 2. JwtAuthenticationFilter处理
String token = request.getHeader("Authorization");
Long userId = TokenUtil.getUserId(token);  // 解析出用户ID: 123
String permissions = TokenUtil.getPermissionList(token); // 解析权限: "USER,ADMIN"
// 3. 设置Security上下文
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
    123L, null, AuthorityUtils.commaSeparatedStringToAuthorityList("USER,ADMIN")
);
SecurityContextHolder.getContext().setAuthentication(auth);
// 4. Controller方法执行
@GetMapping("/api/blogs")
public Result<List<Blog>> getBlogs(MyUser currentUser) {
    // currentUser会被PrincipalUserArgumentResolver自动注入
    // currentUser.getId() == 123L
}
🔴 认证失败流程
// 1. 用户发送无效Token请求
GET /api/blogs HTTP/1.1
Authorization: Bearer invalid_token
// 2. TokenUtil验证失败,userId为null
Long userId = TokenUtil.getUserId("invalid_token"); // 返回null
// 3. JwtAuthenticationEntryPoint处理
response.setStatus(401);
response.getWriter().write({
    "code": 30001,
    "message": "用户访问资源没有携带正确的token"
});
🟡 权限不足流程
// 1. 普通用户访问管理员接口
GET /api/admin/users HTTP/1.1
Authorization: Bearer valid_token_but_no_admin_permission
// 2. 认证成功,但权限检查失败
// SecurityContext中权限只有"USER",但接口需要"ADMIN"
// 3. JwtAccessDeniedHandler处理
response.setStatus(403);
response.getWriter().write({
    "code": 30002,
    "message": "用户访问没有授权资源"
});
🔧 配置集成
这些组件需要在Security配置中进行集成:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    
    @Autowired
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 异常处理配置
            .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)  // 认证失败处理
                .accessDeniedHandler(jwtAccessDeniedHandler)            // 权限不足处理
            .and()
            // 添加JWT过滤器
            .addFilterBefore(
                new JwtAuthenticationFilter(authenticationManager()), 
                UsernamePasswordAuthenticationFilter.class
            );
    }
    
    // 注册参数解析器
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new PrincipalUserArgumentResolver());
    }
}
💡 总结
这四个文件构成了完整的JWT认证授权体系:
| 组件 | 职责 | 何时触发 | 
|---|---|---|
| JwtAuthenticationFilter | 🔍 提取和验证Token | 每个HTTP请求 | 
| JwtAuthenticationEntryPoint | ❌ 处理认证失败 | Token无效/缺失时 | 
| JwtAccessDeniedHandler | 🚫 处理权限不足 | 认证成功但权限不够时 | 
| PrincipalUserArgumentResolver | 👤 注入当前用户 | Controller方法需要用户参数时 | 
这种设计的优势:
- ✅ 无状态认证: 服务器不需要存储session
- ✅ 统一异常处理: 认证和授权错误有统一的响应格式
- ✅ 开发友好: Controller可以直接获取当前用户,无需手动解析
- ✅ 扩展性强: 可以轻松添加更多的安全策略
    前端工程师、程序员

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号