目录
1、JWT简介
JWT是JSON Web Token的缩写,是一个开放标准(RFC 7519),它定义了一种紧凑和自包含的方式,用于作为JSON对象在各方之间安全地传输信息。JWT可以用于身份验证和授权,因为它是数字签名的。
github: https://github.com/jwtk/jjwt
1.1、Session+Cookie认证
http 协议本身是无状态的协议,就意味着当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过 http 协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息 ,这就需session。Session认证是一种基于服务器端的认证方式,它是通过在服务器端保存用户会话信息来实现的。当用户第一次访问服务器时,服务器会生成一个Session ID并将其存储在Cookie中。用户在后续的请求中将会带上这个Cookie,服务器可以通过这个Cookie来识别用户并获取用户的会话信息。
1.2、Token认证
token认证方式跟 session 的方式流程差不多,不同的地方在于保存的是一个 token 值到 redis,token 一般是一串随机的字符(比如UUID),value 一般是用户ID,并且设置一个过期时间。每次请求服务的时候带上 token 在请求头,后端接收到token 则根据 token 查一下 redis 是否存在,如果存在则表示用户已认证,如果 token 不存在则跳到登录界面让用户重新登录,登录成功后返回一个 token 值给客户端。
1.3、对比
- 在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
- JWT方式校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录,验证token更为简单
2、JWT组成
JWT 由三部分组成:头部(Header)、**载荷(Payload)
**和 签名(Signature),从官方的图解也可以看到如下:(JWT官网:JSON Web Tokens - jwt.io)

头部(Header)
头部(Header): JWT头部通常由两部分组成,第一部分是声明类型,例如JWT,第二部分是声明所使用的算法,例如HMAC SHA256或者RSA等。 最后使用 Base64 URL 算法将上述 JSON 对象转换为字符串保存 。
{
"alg": "HS256", // 签名算法
"typ": "JWT" // 令牌类型
}
载荷(Payload)
携带一些用户信息 和默认字段; 默认情况下 JWT 是未加密的,任何人都可以解读其内容,因此一些敏感信息不要存放于此,以防信息泄露。 JSON 对象也使用 Base64 URL 算法转换为字符串后保存,可以反编码。
/**
ss (issuer):签发人/发行人
sub (subject):主题
aud (audience):用户
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
iat (Issued At):签发时间
jti (JWT ID):用于标识该 JWT
**/
// 自定义
{
//默认字段
"sub":"主题123",
//自定义字段
"name":"gcxy",
"isAdmin":"true",
"loginTime":"2023-8-22 10:00:03"
}
签名(Signature)
防止Token被篡改、确保安全性 ,签名过程:
1.将头部和载荷按照顺序拼接成一个字符串;
2.使用私钥对拼接后的字符串进行加密,得到一个签名;
3.将签名添加到头部中。
3、执行流程
jwt执行流程大致如下:

代码实例:
导入依赖:
<!-- JWT依赖 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
封装JWT工具类
package com.gcxy.demo3.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 org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
@Component
public class JwtUtil {
private static String secret = "1234";
/**
* 生成token
* @param subject
* @return
*/
public static String createToken(Map<String, String> subject){
// 过期时间
Date date = new Date(new Date().getTime() + 3600 * 1000);
JWTCreator.Builder builder = JWT.create();
subject.forEach((k, v) -> {
builder.withClaim(k, v);
});
// 设置过期时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 1); // 设置默认过期时间
builder.withExpiresAt(calendar.getTime()); // 指定过期时间
return builder.sign(Algorithm.HMAC256(secret));
}
/**
* 校验token
* @param token
* @return
*/
public static DecodedJWT verifyToken(String token) {
return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
}
/**
* 获取token信息
* @param token
* @return
*/
public static Map<String, Claim> getTokenInfo(String token) {
return JWT.require(Algorithm.HMAC256(secret)).build().verify(token).getClaims();
}
}
创建拦截 器:
package com.gcxy.demo3.interceptor;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gcxy.demo3.utils.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Resource
private JwtUtil jwtConfig;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HashMap<String, String> map = new HashMap<>();
// Token 验证
String token = request.getHeader("Authorization");
try {
// 校验成功放行请求
DecodedJWT verifyToken = jwtConfig.verifyToken(token);
return true;
} catch (Exception e) {
map.put("msg", "token验证失败: " + e);
}
// 校验失败返回失败信息
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(json);
return false;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
}
}
注册拦截器:
package com.gcxy.demo3.config;
import com.gcxy.demo3.interceptor.TokenInterceptor;
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 InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
registry.addInterceptor(new TokenInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login")
;
}
}
创建测试controller
package com.gcxy.demo3.controller;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.gcxy.demo3.utils.JwtUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
public class UserController {
@PostMapping("/login")
public Map<String, Object> login(String username, String password) {
Map<String, Object> map = new HashMap<>();
try{
// 生成token
Map<String, String> payload = new HashMap<>();
payload.put("username", "zhangsan");
payload.put("password", "123456");
String token = JwtUtil.createToken(payload);
map.put("code", 200);
map.put("msg", "登录成功");
map.put("token", token);
}catch (Exception e){
System.out.println(e.getMessage());
map.put("code", 500);
map.put("msg", "登录失败");
}
return map;
}
@GetMapping("/getUserInfo")
public Map<String, Object> getUserInfo(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
String token = request.getHeader("Authorization");
DecodedJWT tokenInfo = JwtUtil.verifyToken(token);
String username = tokenInfo.getClaim("username").asString();
String password = tokenInfo.getClaim("password").asString();
System.out.println("username = " + username);
System.out.println("password = " + password);
map.put("code", 200);
map.put("msg", "请求成功");
return map;
}
}
测试结果:


练习拓展:
结合Redis实现token验证