Spring Boot 和 Spring Security 实现 JWT 认证

发布于:2025-06-18 ⋅ 阅读:(21) ⋅ 点赞:(0)

一、 JWT(JSON Web Token)

1.JWT 基本概念

JWT 是一种开放标准(RFC 7519),用于在网络应用间安全传递 JSON 格式的声明信息。其核心特点包括:

  1. 紧凑性:通过 Base64URL 编码生成字符串,可通过 URL、HTTP Header 或 POST 参数传输。
  2. 自包含:负载(Payload)直接携带用户信息(如 ID、角色),减少服务端查询数据库的开销。
  3. 数字签名:使用密钥(HMAC)或公钥/私钥(RSA)签名,确保信息未被篡改。
2.JWT 结构

JWT 由三部分组成,以点号 . 分隔:Header.Payload.Signature

  1. Header(头部)

    • 描述令牌类型(typ: "JWT")和签名算法(如 alg: HS256),Base64URL 编码后形成第一部分。
    { "alg": "HS256", "typ": "JWT" }
    
  2. Payload(负载)

    • 存储声明(Claims),包含用户数据及标准声明(如 sub 用户 ID、exp 过期时间)。
    • 声明类型
      • 注册声明(可选):预定义字段(iss 签发者、aud 受众)。
      • 公共声明:自定义字段(如 name: "John")。
      • 私有声明:系统内部使用的自定义字段。

    注意:Payload 仅 Base64URL 编码,未加密,避免存储敏感信息(如密码)。

  3. Signature(签名)

    • 对编码后的 Header 和 Payload 用指定算法(如 HMAC-SHA256)签名,密钥由服务端保管:
    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
    
    • 签名验证数据完整性和来源真实性。
3.JWT 工作原理
  1. 认证阶段
    • 用户提交凭证(用户名/密码)→ 服务端验证通过 → 生成 JWT 返回客户端。
  2. 客户端存储
    • 客户端将 JWT 存入 localStoragesessionStorage 或 Cookie(建议 HttpOnly 防 XSS)。
  3. 请求携带
    • 后续请求在 HTTP Header 中添加:Authorization: Bearer <JWT>
  4. 服务端验证
    • 校验签名有效性、过期时间(exp)及受众(aud)→ 通过则允许访问资源。
4.JWT vs Session 认证
特性 JWT Session
服务端状态 无状态(信息在 Token 中) 需存储会话信息(内存/数据库)
扩展性 天然支持分布式系统 需 Session 共享机制(如 Redis)
安全性 签名防篡改;但需防 XSS 盗取 Token 依赖 Cookie;易受 CSRF 攻击
性能 减少数据库查询;但 Token 体积较大 需频繁查询会话数据

JWT 在微服务、跨域单点登录(SSO)场景中优势显著。


5.应用场景
  1. 身份认证:用户登录后,JWT 作为无状态凭证。
  2. 单点登录(SSO):一次登录生成 JWT,多系统共享认证状态。

二、Spring Security 集成 JWT

  1. 认证流程

    • 用户登录:客户端提交凭证 → 服务端验证并生成 JWT
    • 请求携带:客户端在 Authorization: Bearer <token> 头中附加 JWT
    • 服务端验证:Spring Security 过滤器链解析并验证 JWT 有效性
  2. 技术优势

    • 无状态架构:服务端无需存储会话,适合微服务分布式系统
    • 细粒度授权:通过 JWT Claims 实现角色/权限动态控制
1.JWT 配置参数
jwt:
  secret: "base64-encoded-secret"  # Base64 编码密钥
  expiration: 86400000             # Token 有效期(24小时)
  header: Authorization             # 请求头字段
2.Spring Security 配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()          // 关闭 CSRF
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()  // 放行登录接口
            .anyRequest().authenticated()             // 其他接口需认证
            .and()
            .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class); // 添加 JWT 过滤器
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // 密码加密器
    }
}
3.JWT 工具类实现
@Component
public class JwtUtils {
    @Value("${jwt.secret}")
    private String secret;

    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
            .setSubject(userDetails.getUsername())   // 用户名作为主体
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(SignatureAlgorithm.HS512, secret) // HS512 算法签名
            .compact();
    }

    public Claims parseToken(String token) {
        return Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();
    }
}

签名算法选择

算法类型 适用场景
HS256/HS512 单应用场景(对称加密)
RS256/RS512 多服务调用(非对称加密)

4.认证流程实现
(1)登录接口生成 Token
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginRequest request) {
        // 1. 验证用户名密码
        UserDetails user = userDetailsService.loadUserByUsername(request.getUsername());
        // 调用passwordEncoder验证密码是否一致
        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            return ResponseEntity.status(401).build();
        }
        // 2. 生成 JWT
        String token = jwtUtils.generateToken(user);
        return ResponseEntity.ok(token);
    }
}

新增用户时使用PasswordEncoder加密密码。

(2)JWT 认证过滤器
public class JwtAuthFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response); // 放行未认证请求
            return;
        }
        String token = header.substring(7);
        Claims claims = jwtUtils.parseToken(token); // 解析 Token
        // 构建 Authentication 对象并存入 SecurityContext
        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
            claims.getSubject(), null, AuthorityUtils.createAuthorityList("ROLE_USER")
        );
        SecurityContextHolder.getContext().setAuthentication(auth);
        chain.doFilter(request, response);
    }
}

在 Spring Security 中,UsernamePasswordAuthenticationToken 的 authorities 参数用于注入当前用户的​​权限集合​​(Collection<? extends GrantedAuthority>)。

6.常见问题与解决方案
(1)Token 失效场景
  • 签名不匹配:检查密钥一致性(确保 Base64 编码正确)
  • 过期时间(exp)未生效:服务器时间不同步 → 使用 NTP 同步
(2)跨域问题(CORS)
@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    config.addAllowedOrigin("https://your-frontend.com");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

网站公告

今日签到

点亮在社区的每一天
去签到