什么是 JWT

在现代Web开发中,身份认证与授权是保障系统安全的核心环节。随着前后端分离架构和微服务的普及,传统基于Session的认证方式面临着跨域、服务器存储压力大等问题。而 JWT(JSON Web Token)作为一种轻量级、无状态的认证方案,凭借其自包含、可跨域、易扩展的特性,逐渐成为主流选择。

JWT全称为JSON Web Token,是基于RFC 7519标准定义的一种紧凑、自包含的令牌格式,用于在不同系统间安全地传递结构化的JSON数据。其核心价值在于“可验证性”“自包含性”

  • 可验证:通过数字签名确保数据未被篡改,接收方可通过签名反向验证令牌合法性;

  • 自包含:令牌本身携带用户身份、权限、有效期等关键信息,无需频繁查询数据库或缓存;

  • 跨平台兼容:基于JSON格式和Base64编码,支持所有主流编程语言和框架;

  • 轻量灵活:体积远小于XML格式的令牌(如SAML),可通过URL、HTTP头或POST参数轻松传输。

跨域认证的问题

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案

在单服务器架构下,上述基于 Session 的认证流程能够稳定运行,但随着互联网服务的发展,分布式系统多域名场景日益普遍,传统 Session 认证逐渐暴露出难以解决的跨域问题

互联网服务离不开用户认证。一般流程是下面这样。

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

在单服务器架构下,上述基于 Session 的认证流程能够稳定运行,但随着互联网服务的发展,分布式系统多域名场景日益普遍,传统 Session 认证逐渐暴露出难以解决的跨域问题

  • 浏览器的 “同源策略” 规定:只有当两个页面的协议、域名、端口完全一致时,才算 “同源”,此时 Cookie 才能正常传递。若用户需要访问的服务分散在不同域名下(例如,用户在a.com登录后,还需访问b.com的资源),由于a.com服务器写入的 Session Cookie 无法被b.com的服务器读取,b.com无法通过 Session ID 识别用户身份,导致用户需要重复登录,严重破坏用户体验。

  • 在分布式架构中,用户的请求可能被分发到不同的服务器节点(例如,通过负载均衡将请求分配给服务器 A 或服务器 B)。传统 Session 数据通常保存在单个服务器的内存中,若用户首次请求被分配到服务器 A 并创建 Session,下次请求若被分配到服务器 B,由于服务器 B 没有该用户的 Session 数据,会判定用户未登录,导致认证失效。

    为解决 Session 共享问题,开发者常采用 “Session 集群”(如 Redis 集中存储 Session)等方案,但这些方案不仅增加了系统的复杂度和运维成本,还可能因集中存储节点的故障引发整个认证系统的可用性风险。

  • 传统 Session 认证依赖 Cookie 实现 Session ID 的传递,但在移动端(如 iOS、Android 应用)中,Cookie 的支持并不完善,且移动端应用更倾向于通过 HTTP 请求头(而非 Cookie)传递认证信息。若强行在移动端使用 Session 认证,需要额外开发适配逻辑,增加了开发成本,同时也可能因 Cookie 的安全性问题(如 CSRF 攻击)给应用带来风险。

为解决传统 Session 认证在跨域、分布式、移动端场景下的缺陷,JSON Web Token(JWT)应运而生。JWT 是一种基于 JSON 的轻量级认证令牌,它将用户身份信息加密后存储在令牌中,通过客户端(如浏览器、移动端 App)主动携带令牌的方式实现认证,无需服务器存储 Session 数据,从根本上解决了跨域认证的核心痛点。

相比传统 Session 认证,JWT 的认证流程更简洁,且天然支持跨域场景,具体步骤如下:

  1. 用户首次登录:用户向服务器发送用户名、密码等认证信息;
  2. 服务器验证与生成 JWT:服务器验证用户信息通过后,根据用户 ID、角色等信息构建 JWT 的载荷,结合头部声明的算法和服务器密钥生成 JWT 令牌;
  3. 服务器返回 JWT:服务器将 JWT 令牌以 JSON 格式(如{“token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…”})返回给客户端,无需写入 Cookie
  4. 客户端存储 JWT:客户端(浏览器、移动端 App)收到 JWT 后,可将其存储在localStorage(浏览器)、SessionStorage或应用内存(移动端)中,而非依赖 Cookie;
  5. 后续请求携带 JWT:用户后续发起请求时,客户端需在 HTTP 请求头中携带 JWT 令牌
  6. 服务器验证 JWT:服务器收到请求后,提取请求头中的 JWT 令牌,按以下步骤验证:
    • 拆分 JWT 的头部、载荷、签名三部分,对头部和载荷进行 Base64 解码;
    • 检查头部声明的签名算法,使用服务器密钥对解码后的头部和载荷重新生成签名;
    • 对比重新生成的签名与 JWT 中的原签名:若一致,说明令牌未被篡改;若不一致,直接拒绝请求;
    • 验证载荷中的exp(过期时间)字段,若当前时间已超过exp,说明令牌已过期,拒绝请求;
    • 验证通过后,从载荷中提取用户身份信息(如userId),基于该信息处理业务逻辑(如返回用户专属数据)。

JWT 解决跨域认证的核心优势​

  • 无状态性:服务器无需存储任何 Session 数据,所有用户身份信息都包含在 JWT 令牌中,降低了服务器的存储压力,同时使分布式系统中的各个节点无需同步 Session 数据,简化了系统架构;​

  • 跨域兼容性:JWT 通过 HTTP 请求头传递,不受浏览器同源策略限制,可轻松支持多域名、跨域接口调用场景(如a.com的前端调用api.b.com的接口);​

  • 移动端友好:无需依赖 Cookie,可在 iOS、Android 等移动端应用中灵活存储和传递,适配性更强;​

  • 安全性可控:通过签名机制保障令牌不被篡改,同时可通过exp字段设置令牌过期时间,降低令牌泄露后的安全风险(即使令牌被窃取,过期后也无法使用)。

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
2
3
4
5
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

JWT 的数据结构

JWT 令牌的完整格式如下:

1
[Header].[Payload].[Signature]

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的

这三个部分分别承担不同的功能,且均采用 Base64URL 编码(一种适用于 URL 的 Base64 变体,替换了部分特殊字符),便于在网络中传输。

头部(Header)

头部用于描述 JWT 的基本信息,主要包含两个字段:

  • alg:声明签名算法(如 HS256、RS256 等)
  • typ:声明令牌类型,固定为 “JWT”

示例头部的 JSON 结构:

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

经过 Base64URL 编码后,上述头部会转换为类似这样的字符串(作为 JWT 的第一部分):

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

载荷(Payload)

载荷是 JWT 的核心部分,用于存储需要传递的用户数据和元信息。它包含三种类型的声明(Claims):

  • 标准声明(Registered Claims):预定义的可选字段,用于描述令牌的基本属性,常见的有:
    • iss:令牌签发者(Issuer)
    • sub:令牌主题(Subject),通常为用户 ID
    • aud:令牌接收者(Audience)
    • exp:令牌过期时间(Expiration Time),以 Unix 时间戳表示
    • nbf:令牌生效时间(Not Before),在此时间前令牌无效
    • iat:令牌签发时间(Issued At)
    • jti:令牌唯一标识符(JWT ID),用于防止重放攻击
  • 公共声明(Public Claims):由开发者自定义的字段,但需注意避免与标准声明冲突,通常用于传递业务相关信息(如用户名、角色等)。
  • 私有声明(Private Claims):由服务端和客户端协商定义的字段,仅在特定场景下使用。

示例载荷的 JSON 结构:

1
2
3
4
5
6
7
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"exp": 1516242622
}

经过 Base64URL 编码后,上述载荷会转换为类似这样的字符串(作为 JWT 的第二部分):

1
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjQyNjIyfQ

请注意,Base64URL 编码是可逆的,他不是安全加密,因此载荷中绝对不能存放敏感信息(如密码、银行卡号等),只能存储非敏感的身份标识或业务数据。

签名(Signature)

签名是 JWT 的安全保障,用于验证令牌在传输过程中是否被篡改,以及确保令牌确实由合法的服务器签发。

签名的生成步骤如下:

  1. 用 Base64URL 编码头部和载荷,得到两个字符串
  2. 将这两个字符串用句号拼接,形成 HeaderEncoded.PayloadEncoded
  3. 使用头部中声明的签名算法(如 HS256),结合服务器端的密钥(Secret)对拼接后的字符串进行加密,生成签名

以 HS256 算法为例,签名的伪代码如下:

1
2
3
4
5
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)

生成的签名字符串作为 JWT 的第三部分,例如:

1
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT 的三段式结构设计既满足了数据传输的简洁性(单一字符串便于在请求头中携带),又通过签名机制确保了数据的完整性和真实性。服务器在验证 JWT 时,只需重新计算签名并与令牌中的签名比对,即可判断令牌是否有效,无需查询数据库或缓存,这也是 JWT 适用于跨域和分布式场景的核心原因。

实际开发中使用 JWT

来到 JWT 官网找找依赖,我们用的比较多的就是右边这两个 Java 的,我们这次就用 jose4j

image-20250923141842714

我们使用这个 jwt 的实现来作为依赖导入我们的项目

1
2
3
4
5
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.9.6</version>
</dependency>

首先,我们需要在配置文件里写上关于 JWT 的一些信息

1
2
3
4
5
6
# 这是生成 JWT 签名时使用的密钥
jwt.secret=your-256-bit-secret-key-for-jwt-token-generation
# 这是 JWT 的过期时间
jwt.expiration=60
# 这是 JWT 的 “签发者”(Issuer)标识,通常是应用的名称或域名,属于 JWT 标准声明
jwt.issuer=aries-app

然后我们创建 JWT 工具类

在这个工具类中,我们一般会创建 jwt 令牌,验证 jwt 令牌,然后从 jwt 令牌中拿出来一些你需要的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package net.onest.aries.util;

import lombok.extern.log4j.Log4j2;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.ErrorCodes;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.HmacKey;
import org.jose4j.lang.JoseException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.HashMap;
import java.util.Map;

/**
* JWT工具类,用于生成和验证JWT令牌
*/
@Component
@Log4j2
public class JwtUtil {

// JWT密钥,实际应用中应该从配置文件中读取
@Value("${jwt.secret:your-256-bit-secret}")
private String secret;

// 令牌过期时间(分钟)
@Value("${jwt.expiration:60}")
private int expiration;

// 令牌签发者
@Value("${jwt.issuer:aries-app}")
private String issuer;

/**
* 生成JWT令牌
* @param userId 用户ID
* @param username 用户名
* @return JWT令牌字符串
*/
public String generateToken(Integer userId, String username) {
try {
// 创建JWT声明
JwtClaims claims = new JwtClaims();
claims.setIssuer(issuer); // 签发者
claims.setAudience("aries-clients"); // 接收者
claims.setExpirationTimeMinutesInTheFuture(expiration); // 过期时间
claims.setGeneratedJwtId(); // 设置JWT ID
claims.setIssuedAtToNow(); // 设置签发时间
claims.setNotBeforeMinutesInThePast(2); // 设置生效时间
claims.setSubject(username); // 设置主题为用户名

// 添加自定义声明
claims.setClaim("userId", userId);

// 创建签名
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setKey(new HmacKey(secret.getBytes()));
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);

// 生成JWT
return jws.getCompactSerialization();
} catch (JoseException e) {
log.error("生成JWT令牌失败", e);
throw new RuntimeException("生成JWT令牌失败", e);
}
}

/**
* 验证JWT令牌
* @param token JWT令牌
* @return 如果验证成功,返回包含声明的Map;否则返回null
*/
public Map<String, Object> validateToken(String token) {
try {
// 创建JWT消费者
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime() // 要求令牌有过期时间
.setAllowedClockSkewInSeconds(100) // 允许100秒的时钟偏差
.setRequireSubject() // 要求令牌有主题
.setExpectedIssuer(issuer) // 验证签发者
.setExpectedAudience("aries-clients") // 验证接收者
.setVerificationKey(new HmacKey(secret.getBytes())) // 设置验证密钥
.setJwsAlgorithmConstraints( // 设置签名算法约束
AlgorithmConstraints.ConstraintType.PERMIT,
AlgorithmIdentifiers.HMAC_SHA256)
.build();

// 处理JWT并验证
JwtClaims jwtClaims = jwtConsumer.processToClaims(token);

// 将声明转换为Map
Map<String, Object> claims = new HashMap<>();
claims.put("sub", jwtClaims.getSubject());
claims.put("userId", jwtClaims.getClaimValue("userId"));
claims.put("exp", jwtClaims.getExpirationTime().getValue());
claims.put("iat", jwtClaims.getIssuedAt().getValue());

return claims;
} catch (InvalidJwtException e) {
// 令牌验证失败
if (e.hasErrorCode(ErrorCodes.EXPIRED)) {
log.warn("JWT令牌已过期: {}", token);
} else {
log.warn("JWT令牌验证失败: {}", e.getMessage());
}
return null;
} catch (MalformedClaimException e) {
log.error("JWT声明格式错误", e);
return null;
}
}

/**
* 从令牌中获取用户名
* @param token JWT令牌
* @return 用户名,如果令牌无效则返回null
*/
public String getUsernameFromToken(String token) {
Map<String, Object> claims = validateToken(token);
return claims != null ? (String) claims.get("sub") : null;
}

/**
* 从令牌中获取用户ID
* @param token JWT令牌
* @return 用户ID,如果令牌无效则返回null
*/
public Integer getUserIdFromToken(String token) {
Map<String, Object> claims = validateToken(token);
return claims != null ? (Integer) claims.get("userId") : null;
}
}

随便写一个 repository

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 用户仓库接口
*/
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

/**
* 根据用户名查找用户
* @param username 用户名
* @return 用户对象
*/
Optional<User> findByUsername(String username);
}

然后把用户服务的接口和实现类写好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 用户服务接口
*/
public interface UserService {

/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return 如果登录成功,返回用户对象;否则返回null
*/
User login(String username, String password);

/**
* 根据用户名查找用户
* @param username 用户名
* @return 用户对象
*/
User findByUsername(String username);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package net.onest.aries.user.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.onest.aries.entity.User;
import net.onest.aries.user.repository.UserRepository;
import net.onest.aries.user.service.UserService;
import org.springframework.stereotype.Service;

/**
* 用户服务实现类
*/
@Service
@RequiredArgsConstructor
@Log4j2
public class UserServiceImpl implements UserService {

private final UserRepository userRepository;

@Override
public User login(String username, String password) {
log.info("用户登录: {}", username);
return userRepository.findByUsername(username)
.filter(user -> user.getPassword().equals(password))
.orElse(null);
}

@Override
public User findByUsername(String username) {
return userRepository.findByUsername(username).orElse(null);
}
}

如果你写了 dto,请务必在登陆响应的dto 中把 token 放进去

接下来我们创建我们登录的控制器,也就是认证控制器,我们使用纯 jwt 验证安全情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package net.onest.aries.user.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.onest.aries.entity.User;
import net.onest.aries.user.dto.LoginRequest;
import net.onest.aries.user.dto.LoginResponse;
import net.onest.aries.user.service.UserService;
import net.onest.aries.util.JwtUtil;
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;

/**
* 认证控制器
*/
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Log4j2
public class AuthController {

private final UserService userService;
private final JwtUtil jwtUtil;

/**
* 用户登录
* @param loginRequest 登录请求
* @return 登录响应
*/
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
log.info("用户登录请求: {}", loginRequest.getUsername());

// 验证用户名和密码
User user = userService.login(loginRequest.getUsername(), loginRequest.getPassword());

if (user == null) {
log.warn("登录失败: 用户名或密码错误 - {}", loginRequest.getUsername());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("用户名或密码错误");
}

// 生成JWT令牌
String token = jwtUtil.generateToken(user.getId(), user.getUsername());

// 返回登录响应
LoginResponse response = new LoginResponse(token, user.getUsername(), user.getId());
log.info("用户登录成功: {}", user.getUsername());

return ResponseEntity.ok(response);
}
}

我们再写一个JWT拦截器,用于验证请求中的JWT令牌,调用的还是上面 JwtUtil 的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package net.onest.aries.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.onest.aries.util.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

/**
* JWT拦截器,用于验证请求中的JWT令牌
*/
@Component
@RequiredArgsConstructor
@Log4j2
public class JwtInterceptor implements HandlerInterceptor {

private final JwtUtil jwtUtil;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中获取Authorization
String authHeader = request.getHeader("Authorization");

// 如果没有Authorization头或者不是Bearer类型,则拒绝请求
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.warn("请求被拒绝: 缺少有效的Authorization头");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("未授权: 缺少有效的令牌");
return false;
}

// 提取令牌
String token = authHeader.substring(7);

// 验证令牌
if (jwtUtil.validateToken(token) == null) {
log.warn("请求被拒绝: 无效的JWT令牌");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("未授权: 无效的令牌");
return false;
}

// 从令牌中获取用户信息并设置到请求属性中
String username = jwtUtil.getUsernameFromToken(token);
Integer userId = jwtUtil.getUserIdFromToken(token);

request.setAttribute("username", username);
request.setAttribute("userId", userId);

log.info("JWT验证通过: 用户 {} (ID: {})", username, userId);
return true;
}
}

接下来我们写一个配置类,标记一下都拦截什么地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Web配置类,用于注册拦截器
*/
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

private final JwtInterceptor jwtInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**") // 拦截所有/api/路径下的请求
.excludePathPatterns("/api/auth/**"); // 排除认证相关的路径
}
}

写一个受保护的 api,看看 jwt 能否生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package net.onest.aries.user.controller;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.onest.aries.entity.User;
import net.onest.aries.user.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
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")
@RequiredArgsConstructor
@Log4j2
public class UserController {

private final UserService userService;

/**
* 获取当前用户信息
* @param request HTTP请求
* @return 用户信息
*/
@GetMapping("/me")
public ResponseEntity<?> getCurrentUser(HttpServletRequest request) {
// 从请求属性中获取用户信息(由JWT拦截器设置)
String username = (String) request.getAttribute("username");
Integer userId = (Integer) request.getAttribute("userId");

log.info("获取当前用户信息: {} (ID: {})", username, userId);

// 查询用户详细信息
User user = userService.findByUsername(username);

if (user == null) {
log.warn("用户不存在: {}", username);
return ResponseEntity.notFound().build();
}

// 构建响应
Map<String, Object> response = new HashMap<>();
response.put("id", user.getId());
response.put("username", user.getUsername());
response.put("email", user.getEmail());

return ResponseEntity.ok(response);
}
}

懒得搞数据库了,因为是从老项目上摘取然后改的,估计是差不多能对的

你可以创建一个 POST 请求试试

  • URL: http://localhost:8080/api/auth/login

  • Headers: Content-Type: application/json

  • Body (raw JSON):

    1
    2
    3
    4
    {
    "username": "testuser",
    "password": "password"
    }

发送请求,如果成功,你将收到类似以下的响应:

1
2
3
4
5
6
{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"username": "testuser",
"userId": 1
}

保存返回的token,我们将在后续请求中使用它

然后创建一个GET请求:

  • URL: http://localhost:8080/api/users/me
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... (使用上一步获取的token)

发送请求,如果JWT验证成功,你将收到用户信息

可以使用在线工具如 jwt.io 来解析和验证JWT令牌