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 存储在 localStoragetoken 中:

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");  
    }  
}

路径拦截与放行遵循「白名单优先拦截」规则,即如果一个路径既不在放行也不在拦截里,那么默认按放行处理。所以这里我们先将所有路径全部拦截,再单独放行登录与注册两条路径。


运行测试,发现能够顺利运行和拦截。



更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

北沐清 微信支付

微信支付

北沐清 支付宝

支付宝