JWT(JSON Web Token)也就是通过 JSON 形式作为 Web 应用中的令牌,用于在各方之间安全地将信息作为 JSON 对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。常用于判断用户是否已经登录。
# 添加依赖
在  pom.xml  中添加 JWT 相关依赖。
<dependency>   | |
<groupId>io.jsonwebtoken</groupId>  | |
<artifactId>jjwt</artifactId>  | |
<version>0.7.0</version>  | |
</dependency>   | |
<dependency>   | |
<groupId>com.auth0</groupId>  | |
<artifactId>java-jwt</artifactId>  | |
<version>3.4.0</version>  | |
</dependency> | 
排雷:如果 ds 或者 gpt 让你加什么 Spring Security 依赖,不要加!会出现很多莫名其妙的问题!
# 关键类
共有三个类:Utils 包下的 JwtUtils 工具类Interceptor 包下的 JwtInterceptor 拦截类Config 包下的 InterceptConfig 配置类
# JwtUtils 以及前端处理 token 方式
用于生成 token、校验 token 以及获取 token 内信息。
话不多说直接丢代码:
package com.example.demo_mysql.Utils;  | |
import com.auth0.jwt.JWT;  | |
import com.auth0.jwt.JWTCreator;  | |
import com.auth0.jwt.algorithms.Algorithm;  | |
import com.auth0.jwt.interfaces.Claim;  | |
import com.auth0.jwt.interfaces.DecodedJWT;  | |
import java.util.Calendar;  | |
import java.util.Map;  | |
public class JwtUtils {  | |
    /**   | |
* 密钥要自己保管好  | |
*/  | |
private static String SECRET = "!Q@W#E$R";  | |
    /**   | |
* 传入 payload 信息获取 token  | |
* * @param map payload  | |
* @return token  | |
* */  | |
public static String getToken(Map<String, String> map) {  | |
JWTCreator.Builder builder = JWT.create();  | |
        //payload   | |
map.forEach((k, v) -> {  | |
builder.withClaim(k, v);  | |
});  | |
Calendar instance = Calendar.getInstance();  | |
instance.add(Calendar.DATE, 3); // 默认 3 天过期  | |
builder.withExpiresAt(instance.getTime());// 指定令牌的过期时间  | |
return builder.sign(Algorithm.HMAC256(SECRET));  | |
    }   | |
    /**   | |
* 验证 token 合法性  | |
*/  | |
public static DecodedJWT verify(String token) {  | |
        // 如果有任何验证异常,此处都会抛出异常   | |
return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);  | |
    }   | |
    /**   | |
* 获取 token 信息方法  | |
*/  | |
public static Map<String, Claim> getTokenInfo(String token) {  | |
return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getClaims();  | |
    }   | |
} | 
对于生成 token 的 getToken 函数,传入的 map 数据用于写入 token,不推荐写入如 password 的敏感信息。
Controller 使用用例:
@GetMapping("/login")  | |
public Map<String, Object> login(User user){  | |
log.info(user.getUsername());  | |
log.info(user.getPassword());  | |
Map<String, Object> map = new HashMap<>();  | |
try{  | |
User login = userService.login(user);  | |
Map<String, String> payload = new HashMap<>();  | |
payload.put("id", String.valueOf(login.getId()));  | |
payload.put("password", login.getPassword());  | |
        // 生成 token   | |
String token = JwtUtils.getToken(payload);  | |
map.put("status", true);  | |
map.put("msg", "登录成功");  | |
map.put("token", token); // 响应 token,存储在客户端  | |
}catch (Exception e) {  | |
log.info("登录失败...");  | |
map.put("status", false);  | |
map.put("msg", "登录失败");  | |
    }   | |
return map;  | |
} | 
# 前端处理 token
前端需要将获得的 token 存储在 localStorage 的 token 中:
localStorage.setItem('token', response.data.token);  | 
此外,前端在每次发送请求的时候都需要将 token 加在请求头中,这里我们加在标准的 Authorization 头中,此外还需要以 Bearer 为开头。我们以 axios 为例,创建 axiosInstance 的 axios 实例:
import axios from 'axios';  | |
// 创建 axios 实例   | |
const axiosInstance = axios.create({  | |
baseURL: 'http://127.0.0.1:8139/' // 本地调试 URL  | |
});  | |
// 设置请求拦截器   | |
axiosInstance.interceptors.request.use(  | |
config => {  | |
        // 获取 JWT token   | |
const token = localStorage.getItem('token');  | |
if (token) {  | |
            // 将 token 添加到请求头部   | |
config.headers.Authorization = `Bearer ${token}`;  | |
        }   | |
return config;  | |
},  | |
error => {  | |
return Promise.reject(error);  | |
    }   | |
);  | |
// 设置响应拦截器   | |
axiosInstance.interceptors.response.use(  | |
response => {  | |
return response;  | |
},  | |
error => {  | |
if (error.response && error.response.status === 400 || error.response.status === 401) {  | |
            // 如果响应状态码为 400 或 401,则跳转到登录页面   | |
localStorage.removeItem('token');  | |
window.location.href = `/login`;  | |
alert('您还未登录。')  | |
        }   | |
    }   | |
)   | |
export default axiosInstance;  | 
# JwtInterceptor
JwtInterceptor 用于校验 token 是否合法,从而拦截不合法的请求。
package com.example.demo_mysql.Interceptor;  | |
import com.auth0.jwt.interfaces.DecodedJWT;  | |
import com.example.demo_mysql.Utils.JwtUtils;  | |
import com.fasterxml.jackson.databind.ObjectMapper;  | |
import jakarta.servlet.http.HttpServletRequest;  | |
import jakarta.servlet.http.HttpServletResponse;  | |
import org.springframework.web.servlet.HandlerInterceptor;  | |
import java.util.HashMap;  | |
public class JwtInterceptor implements HandlerInterceptor {  | |
    @Override   | |
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  | |
HashMap<String, String> map = new HashMap<>();  | |
        // 从请求头中提取 Authorization 值   | |
String authHeader = request.getHeader("Authorization");  | |
        // 检查 Authorization 头是否存在且格式合法   | |
if (authHeader == null || !authHeader.startsWith("Bearer ")) {  | |
			// 失败返回 401 错误码 | |
System.out.println("验证失败,头不存在");  | |
map.put("msg", "验证失败,头不存在");  | |
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401  | |
sendJsonResponse(response, map);  | |
return false;  | |
        }   | |
        // 提取 token(去除 "Bearer" 前缀)   | |
String token = authHeader.substring(7);  | |
try {  | |
            // 如果验证成功放行请求   | |
DecodedJWT verify = JwtUtils.verify(token);  | |
return true;  | |
} catch (Exception exception) {  | |
	        // 失败返回 401 错误码 | |
System.out.println("验证失败:" + exception);  | |
map.put("msg", "验证失败:" + exception);  | |
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);  | |
sendJsonResponse(response, map);  | |
return false;  | |
        }   | |
    }   | |
	// 封装发送 JSON 响应的逻辑 | |
private void sendJsonResponse(  | |
HttpServletResponse response,  | |
HashMap<String, String> map  | |
) throws Exception {  | |
response.setContentType("application/json;charset=UTF-8"); // 修正 Content-Type  | |
String json = new ObjectMapper().writeValueAsString(map);  | |
response.getWriter().write(json); // 写入 JSON 数据  | |
response.getWriter().flush(); // 确保数据发送到客户端  | |
    }   | |
} | 
# InterceptConfig
这个类用于注册拦截器,并规定哪些路径需要放行或拦截。
package com.example.demo_mysql.Config;  | |
import com.example.demo_mysql.Interceptor.JwtInterceptor;  | |
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 InterceptConfig implements WebMvcConfigurer {  | |
    @Override   | |
public void addInterceptors(InterceptorRegistry registry) {  | |
        // 添加拦截器   | |
registry.addInterceptor(new JwtInterceptor())  | |
                // 拦截的路径即需要进行 token 验证的路径。这里配置全部拦截 | |
.addPathPatterns("/**")  | |
                // 放行的路径   | |
.excludePathPatterns("/login", "/user_create");  | |
    }   | |
} | 
路径拦截与放行遵循「白名单优先拦截」规则,即如果一个路径既不在放行也不在拦截里,那么默认按放行处理。所以这里我们先将所有路径全部拦截,再单独放行登录与注册两条路径。
运行测试,发现能够顺利运行和拦截。