一、介绍
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输声明。它通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),形式为 Header.Payload.Signature
。下面为你详细介绍它的相关知识。
二、结构
- 头部(Header):通常由两部分组成,令牌的类型(即 JWT)和使用的签名算法,如 HMAC SHA256 或 RSA。它会被 Base64Url 编码形成 JWT 的第一部分。
- 载荷(Payload):包含声明(Claims),声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册声明、公开声明和私有声明。同样,它也会被 Base64Url 编码形成 JWT 的第二部分。
- 签名(Signature):为了创建签名部分,你需要使用编码后的头部、编码后的载荷、一个秘钥(secret)和头部中指定的签名算法。签名用于验证消息在传输过程中没有被更改,并且在使用私钥签名的情况下,还可以验证 JWT 的发送者的身份。
三、工作流程
- 用户登录时,服务器验证用户的身份。
- 如果验证成功,服务器会创建一个 JWT 并将其返回给客户端。
- 客户端在后续的请求中都会携带这个 JWT,通常是在请求头的
Authorization
字段中。 - 服务器接收到请求后,会验证 JWT 的签名和有效性,如果验证通过,则处理请求并返回响应。
四、优点
- 无状态:JWT 是无状态的,服务器不需要存储会话信息,这使得它非常适合于微服务架构和分布式系统。
- 跨域支持:由于 JWT 可以在请求头中传输,因此它可以很方便地用于跨域请求。
- 可扩展性:JWT 的载荷部分可以包含任意的声明,因此可以根据需要扩展其功能。
五、缺点
- 安全性问题:如果 JWT 的秘钥泄露,攻击者可以伪造 JWT。此外,JWT 一旦发布,在过期之前无法撤销。
- 体积较大:由于 JWT 包含了头部、载荷和签名,因此它的体积相对较大,会增加请求的大小。
六、使用场景
- 身份验证:在用户登录后,服务器可以生成一个 JWT 并返回给客户端,客户端在后续的请求中携带这个 JWT,服务器通过验证 JWT 来确认用户的身份。
- 信息交换:JWT 可以在不同的服务之间安全地传输信息,因为签名可以确保信息的完整性和真实性。
七、实例
下面为你提供一个使用 Spring Boot 3 结合 com.auth0:java-jwt
库实现 JWT 认证的 Web 实例。该实例包含用户注册、登录、生成 JWT 以及使用拦截器验证 JWT 等功能。
pom.xml
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Java-JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
JwtUtils.java
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class JwtUtils {
private static final String SECRET = "yourSecretKey";
private static final long EXPIRATION_TIME = 86400000; // 24 hours
public static String generateToken(String username) {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
return JWT.create()
.withSubject(username)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(algorithm);
}
public static String validateToken(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getSubject();
} catch (JWTVerificationException e) {
return null;
}
}
}
WebMvcConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/users/register", "/api/users/login");
}
}
JwtInterceptor.java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String tokenHeader = request.getHeader("Authorization");
if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) {
String token = tokenHeader.substring(7);
String username = JwtUtils.validateToken(token);
if (username != null) {
return true;
}
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
UserController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody User user) {
if (userService.findByUsername(user.getUsername()) != null) {
return new ResponseEntity<>("Username already exists", HttpStatus.BAD_REQUEST);
}
userService.save(user);
return new ResponseEntity<>("User registered successfully", HttpStatus.CREATED);
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody User user) {
User existingUser = userService.findByUsername(user.getUsername());
if (existingUser != null && existingUser.getPassword().equals(user.getPassword())) {
String token = JwtUtils.generateToken(user.getUsername());
Map<String, String> response = new HashMap<>();
response.put("token", token);
return new ResponseEntity<>(response, HttpStatus.OK);
}
return new ResponseEntity<>("Invalid credentials", HttpStatus.UNAUTHORIZED);
}
}
当然这之前也要有现实的 User
类,UserService
类,UserServiceImpl
类
代码说明
pom.xml
:添加了 Spring Boot Web、java-jwt
、Spring Data JPA 和 H2 数据库依赖。JwtUtils.java
:使用com.auth0:java-jwt
库生成和验证 JWT。UserController.java
:处理用户注册和登录请求,登录成功后返回 JWT。JwtInterceptor.java
:实现HandlerInterceptor
接口,在请求处理前验证 JWT。WebMvcConfig.java
:将JwtInterceptor
注册到 Spring MVC 的拦截器链中,对/api/**
路径进行拦截,排除注册和登录接口。
运行步骤
- 创建一个 Spring Boot 3 项目,并将上述代码添加到相应的文件中。
- 启动 Spring Boot 应用程序。
- 发送注册请求到
/api/users/register
接口,注册新用户。 - 发送登录请求到
/api/users/login
接口,获取 JWT。 - 在后续请求的
Authorization
头中添加Bearer <JWT>
进行身份验证。
希望对你有帮助!