- 引言
随着 Web 服务的发展,如何保障系统的安全成为关键问题。在分布式系统和 RESTful API 中,传统的会话(Session)认证方式往往存在扩展性问题,因此 JWT(JSON Web Token) 逐渐成为主流的认证解决方案。Spring Boot 作为一个轻量级的微服务框架,能够非常方便地与 JWT 集成,实现用户认证和授权。本文将深入讲解 JWT 的工作原理、Spring Boot 中的具体实现步骤,以及一些常见的安全问题和最佳实践。
- JWT 简介
2.1. 什么是 JWT?
JWT(JSON Web Token)是一种开放的标准(RFC 7519),用于在客户端与服务器之间传递经过签名的令牌信息。它具有以下特点:
- 无状态:JWT 本质上是一段自包含的 JSON 数据,不需要服务器端存储会话状态。
- 可验证性:JWT 使用签名或加密方式保证数据不可篡改。
- 可扩展性:JWT 中可以包含用户角色、权限等自定义声明(Claims),以支持复杂的权限控制。
2.2. JWT 的结构
JWT 的令牌结构由三个部分组成,用 . 连接:
header.payload.signature
- Header:声明令牌的类型和签名算法,如 {"alg": "HS256", "typ": "JWT"}。
- Payload:存储声明信息(如用户 ID、角色等)。
- Signature:用于保证令牌完整性。
一个示例 JWT 如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTY1MDYzMzY4NCwiZXhwIjoxNjUwNjM0Mjg0fQ.QMQrcyVOaG-ovIDN6MEss9O9afDAZJ_8Ooa8Ar0exCk
- Spring Boot 集成 JWT 实现用户认证
下面我们将使用 Spring Boot 和 JWT 来实现一个完整的用户登录认证流程,包括用户登录、生成 JWT 令牌、请求拦截以及权限控制。
3.1. 引入依赖
我们需要在 pom.xml 中添加必要的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
jjwt 是一个用于生成和解析 JWT 的 Java 库。
3.2. 配置安全过滤器和安全配置
3.2.1. 自定义 JWT 过滤器
我们需要在每次请求时解析 JWT,并基于令牌中的信息来决定是否允许访问:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
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;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final String secretKey = "yourSecretKey";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
try {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token.substring(7))
.getBody();
// 将用户信息存入上下文
SecurityContextHolder.getContext().setAuthentication(
new JwtAuthenticationToken(claims.getSubject(), claims.get("role"))
);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
filterChain.doFilter(request, response);
}
}
3.2.2. 配置 Spring Security
我们需要将自定义的过滤器加入到 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.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter());
return http.build();
}
}
3.3. 实现用户登录与 JWT 生成
3.3.1. 登录控制器
当用户登录成功后,我们将生成一个 JWT,并将其返回给客户端:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RestController
public class AuthController {
private final String secretKey = "yourSecretKey";
@PostMapping("/login")
public Map<String, String> login(@RequestParam String username, @RequestParam String password) {
// 模拟用户认证(实际应用中应查询数据库)
if ("admin".equals(username) && "password".equals(password)) {
String token = Jwts.builder()
.setSubject(username)
.claim("role", "ADMIN")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时过期
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
Map<String, String> response = new HashMap<>();
response.put("token", "Bearer " + token);
return response;
} else {
throw new RuntimeException("Invalid credentials");
}
}
}
- 使用 JWT 进行请求验证
客户端在后续请求中需要在 Authorization 头中携带 JWT:
curl -H "Authorization: Bearer <token>" http://localhost:8080/protected-resource
Spring Security 会通过自定义的过滤器验证令牌,并根据令牌中的用户信息进行权限控制。
- 常见问题与最佳实践
5.1. JWT 的失效控制
由于 JWT 是无状态的,无法直接在服务端销毁。为了解决这一问题,我们可以:
- 设置合理的过期时间,比如 1 小时。
- 黑名单机制:将已注销或失效的 JWT 存入 Redis 黑名单。
5.2. 刷新令牌(Refresh Token)
为了避免频繁登录,我们可以为用户提供 刷新令牌,允许用户在访问令牌过期后获取新的 JWT。
5.3. 使用 HTTPS 加密传输
JWT 中可能包含敏感信息,因此建议在生产环境中使用 HTTPS 传输,避免令牌在网络传输中被截获。
5.4. 避免过度暴露用户信息
不要在 JWT 中存储敏感的用户数据,只存放必要的声明信息,如用户 ID 和角色。
- 总结
通过本文的讲解,我们了解了如何在 Spring Boot 中集成 JWT 进行用户认证,包括 JWT 的生成、验证、过滤器的配置以及登录逻辑的实现。JWT 的无状态特性使得它非常适合微服务架构和分布式系统,但在使用时也需要注意其安全性和有效性管理。在实际项目中,结合 Redis 黑名单、刷新令牌等机制,可以进一步提升系统的安全性和用户体验。