1.什么是JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境间安全传递声明的紧凑且自包含的方式。它由三部分组成:
Header(头部) – 说明令牌类型和签名算法
Payload(负载) – 包含实际的用户数据和其他声明
Signature(签名) – 用于验证消息的完整性
典型JWT格式:xxxxx.yyyyy.zzzzz
(Base64Url编码的三部分用点连接)
2.JWT能解决什么问题?
认证问题:替代传统的session-cookie机制
服务端不再需要存储session状态
天然支持无状态分布式架构
跨域/跨服务认证:
适合微服务架构下的单点登录(SSO)
适合前后端分离项目
信息自包含:
包含用户基本信息和权限声明
减少数据库查询次数
安全传输:
通过签名防止数据篡改
可选择加密保护敏感信息
3.使用步骤(以Spring Boot + Java JWT为例)
1. 添加依赖(pom.xml)
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
2. 创建JWT工具类
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
public class JwtUtil {
// 密钥(应存储在安全配置中)
private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 有效期(毫秒)
private static final long EXPIRATION_TIME = 86400000; // 24小时
// 生成JWT
public static String generateToken(String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("role", role) // 自定义声明
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY)
.compact();
}
// 解析JWT
public static Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
}
// 验证JWT
public static boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
3. 实现登录接口(颁发token)
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// 1. 验证用户名密码(伪代码)
User user = userService.authenticate(request.getUsername(), request.getPassword());
// 2. 生成JWT
String token = JwtUtil.generateToken(user.getUsername(), user.getRole());
// 3. 返回token
return ResponseEntity.ok(new JwtResponse(token));
}
}
@Data
class LoginRequest {
private String username;
private String password;
}
@Data
class JwtResponse {
private String token;
public JwtResponse(String token) {
this.token = token;
}
}
4. 创建JWT验证过滤器
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// 1. 从请求头获取token
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = header.substring(7);
// 2. 验证token
try {
Claims claims = JwtUtil.parseToken(token);
// 3. 创建认证对象
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
claims.getSubject(),
null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + claims.get("role")))
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 4. 设置安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
// token无效
SecurityContextHolder.clearContext();
}
filterChain.doFilter(request, response);
}
}
5. 配置Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.anyRequest().authenticated();
}
}
4.JWT的安全实践
密钥保护:
使用足够强度的密钥(HS256至少32字节)
不要硬编码密钥,使用安全配置管理
有效期控制:
设置合理的expiration时间
实现refresh token机制更新token
防止盗用:
使用HTTPS传输
将token存储在HttpOnly cookie中(防XSS)
敏感数据:
Payload中不要存储密码等敏感信息
必要时可以使用JWE(JSON Web Encryption)加密
5.常见问题解决方案
问题1:Token失效问题
场景:用户修改密码后旧token仍有效
解决方案:
维护token黑名单
使用JWT的jti(唯一标识)配合redis记录有效token
问题2:Token被盗
防护措施:
实现IP绑定检查
添加指纹识别(browser fingerprint)
问题3:服务端注销
方案对比:
方案 | 优点 | 缺点 |
---|---|---|
短期令牌 | 实现简单 | 频繁重新认证 |
令牌黑名单 | 即时生效 | 需要额外存储 |
双令牌机制 | 平衡安全与体验 | 实现较复杂 |
暂无评论内容