基于 JWT 的登录认证
JWT(JSON Web Token)是一种用于身份验证和授权的开放标准(RFC 7519),它通过 JSON 对象在客户端和服务器之间安全地传输信息。JWT 通常用于实现无状态的登录认证机制。
以下是关于 JWT 令牌 和 登录认证 的详细说明:
1. JWT 的结构
JWT 由三部分组成,用 .
分隔:
- Header(头部):
- 包含令牌的类型(如
JWT
)和签名算法(如HS256
)。 - 示例:
- 包含令牌的类型(如
{
"alg": "HS256",
"typ": "JWT"
}
2.Payload(负载):
- 包含用户信息和其他声明(如过期时间、签发者等)。
- 示例:
{
"sub": "1234567890", // 用户 ID
"name": "John Doe", // 用户名
"iat": 1516239022, // 签发时间
"exp": 1516242622 // 过期时间
}
3.Signature(签名):
- 对头部和负载进行签名,确保令牌未被篡改。
- 签名算法:
HMAC SHA256
或RSA SHA256
。 - 示例:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
最终的 JWT 格式:
Header.Payload.Signature
2. JWT 的工作流程
- 用户登录:
- 用户提交用户名和密码。
- 服务器验证用户名和密码,如果验证通过,生成 JWT 并返回给客户端。
- 客户端存储 JWT:
- 客户端(如浏览器)将 JWT 存储在
localStorage
、sessionStorage
或Cookie
中。
- 客户端(如浏览器)将 JWT 存储在
- 客户端发送 JWT:
- 客户端在每次请求时,将 JWT 放在请求头中(通常是
Authorization
字段)。
- 客户端在每次请求时,将 JWT 放在请求头中(通常是
- 服务器验证 JWT:
- 服务器从请求头中提取 JWT,验证其签名和有效期。
- 如果 JWT 有效,允许请求继续;否则返回
401 Unauthorized
。
3. JWT 的优点
- 无状态:服务器不需要存储会话信息,适合分布式系统。
- 安全性:通过签名防止篡改。
- 跨域支持:适合单点登录(SSO)和跨域认证。
- 灵活性:可以在负载中存储自定义信息。
4. JWT 的缺点
- 无法撤销:一旦签发,在过期前无法撤销(除非使用黑名单机制)。
- 负载大小:JWT 比传统的 Session ID 大,可能增加网络开销。
- 安全性依赖密钥:如果密钥泄露,JWT 可能被伪造。
5. 实现登录认证的步骤
以下是基于 JWT 的登录认证实现步骤:
(1)用户登录
- 客户端发送用户名和密码到服务器。
- 服务器验证用户名和密码,如果验证通过,生成 JWT 并返回。
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest) {
// 验证用户名和密码
User user = userService.findByUsername(loginRequest.getUsername());
if (user == null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
return ResponseEntity.status(401).body("Invalid username or password");
}
// 生成 JWT
String token = JwtUtil.generateToken(user.getId(), user.getUsername());
return ResponseEntity.ok(token);
}
(2)生成 JWT
- 使用工具类生成 JWT。
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key"; // 密钥
private static final long EXPIRATION_TIME = 86400000; // 过期时间:24 小时
public static String generateToken(Long userId, String username) {
return Jwts.builder()
.setSubject(userId.toString()) // 用户 ID
.claim("username", username) // 自定义声明
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 签名算法和密钥
.compact();
}
}
(3)验证 JWT
- 在拦截器或过滤器中验证 JWT。
public class JwtUtil {
public static Map<String, Object> parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
(4)拦截器验证
- 在拦截器中验证 JWT,并决定是否放行请求。
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
try {
Map<String, Object> claims = JwtUtil.parseToken(token);
return true; // 验证成功,放行
} catch (Exception e) {
response.setStatus(401); // 验证失败,返回 401
return false;
}
}
}
(5)注册拦截器
- 在 Spring 配置中注册拦截器。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login", "/register"); // 排除登录和注册接口
}
}
6. 安全性建议
- 使用 HTTPS:防止 JWT 被窃取。
- 设置合理的过期时间:减少令牌泄露的风险。
- 使用强密钥:确保密钥足够复杂。
- 黑名单机制:如果需要撤销令牌,可以使用黑名单机制。
总结
- JWT 是一种无状态的身份验证机制,适合分布式系统和跨域认证。
- 通过拦截器或过滤器验证 JWT,可以实现登录认证。
- 注意 JWT 的安全性和性能问题。
Spring MVC 拦截器的注册
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/user/login", "/user/register", "/static/**", "/public/**"); // 排除登录、注册和静态资源
}
}
1. 类定义
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Configuration
:- 标记该类为 Spring 的配置类,用于定义 Bean 或配置组件。
WebMvcConfigurer
:- Spring MVC 提供的接口,用于自定义 MVC 配置(如拦截器、视图解析器、消息转换器等)。
2. 注入拦截器
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
:- 自动注入
LoginInterceptor
拦截器对象。
- 自动注入
LoginInterceptor
:- 自定义的拦截器,用于在请求到达控制器之前执行某些逻辑(如身份验证)。
3. 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/user/login", "/user/register", "/static/**", "/public/**"); // 排除登录、注册和静态资源
}
addInterceptors
方法:- 用于注册拦截器并配置拦截规则。
registry.addInterceptor(loginInterceptor)
:- 注册
LoginInterceptor
拦截器。
- 注册
addPathPatterns("/**")
:- 指定拦截器拦截所有路径(
/**
表示匹配所有请求)。
- 指定拦截器拦截所有路径(
excludePathPatterns("/user/login", "/user/register", "/static/\**", "/public/\**")
:- 排除某些路径,不进行拦截:
/user/login
:登录接口。/user/register
:注册接口。/static/**
:静态资源路径(如 CSS、JS、图片等)。/public/**
:公共资源路径。
- 排除某些路径,不进行拦截:
4. 拦截器的作用
- 功能:
- 在请求到达控制器之前执行某些逻辑(如身份验证、日志记录等)。
- 如果拦截器逻辑通过,请求继续执行;否则,拦截请求并返回错误响应。
- 适用场景:
- 身份验证(如 JWT 验证)。
- 权限检查。
- 日志记录。
- 请求参数预处理。
5. 拦截器的工作流程
- 请求到达:
- 客户端发送请求到服务器。
- 拦截器处理:
- 请求首先经过拦截器,执行
preHandle
方法。 - 如果
preHandle
返回true
,请求继续执行;如果返回false
,请求被拦截。
- 请求首先经过拦截器,执行
- 控制器处理:
- 如果请求通过拦截器,到达控制器进行处理。
- 响应返回:
- 控制器处理完成后,返回响应给客户端。