# JWT
**Repository Path**: wl8888/jwt
## Basic Information
- **Project Name**: JWT
- **Description**: 无状态登录和有状态登录介绍,以及JWT案例使用
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-03-01
- **Last Updated**: 2023-05-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
@[TOC](安全认证--JWT介绍及使用)
# 1.无状态登录原理
[有状态登录和无状态登录详解](https://blog.csdn.net/weixin_43811057/article/details/129251264)
## 1.1.什么是有状态?
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。
例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。
缺点是什么?
- 服务端保存大量数据,增加服务端压力
- 服务端保存用户状态,无法进行水平扩展
- 客户端请求依赖服务端,多次请求必须访问同一台服务器
## 1.2.什么是无状态
微服务集群中的每个服务,对外提供的都是Rest风格的接口。而Rest风格的一个最重要的规范就是:服务的无状态性,即:
- 服务端不保存任何客户端请求者信息
- 客户端的每次请求必须具备`自描述信息`,通过这些信息识别客户端身份
带来的好处是什么呢?
- 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
- 服务端的集群和状态对客户端透明
- 服务端可以任意的迁移和伸缩
- 减小服务端存储压力
## 1.3.如何实现无状态
无状态登录的流程:
- 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
- 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
- 以后每次请求,客户端都携带认证的token
- 服务端对token进行解密,判断是否有效。
整个登录过程中,最关键的点是什么?
**token的安全性**
token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。
采用何种方式加密才是安全可靠的呢?
我们将采用`JWT`来生成token,保证token的安全性
## 1.4.JWT
### 1.4.1.简介
JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io
GitHub上jwt的java客户端:https://github.com/jwtk/jjwt
### 1.4.2.数据格式
JWT包含三部分数据:
- Header:头部,通常头部有两部分信息:
- token类型,这里是JWT
- 签名算法,自定义
我们会对头部进行base64加密(可解密),得到第一部分数据
- Payload:载荷,就是有效数据,一般包含下面信息:
- 标准载荷:JWT规定的信息,jwt的元数据:
- JTI: JWT的id,当前jwt的唯一标识(像身份证号)
- IAT: issue at 签发时间
- EXP:过期时间
- SUB:签发人
- ...
- 自定义载荷:
- 用户身份信息,(注意,这里因为采用base64加密,可解密,因此不要存放敏感信息)
这部分也会采用base64加密,得到第二部分数据
- Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性
生成的数据格式:

可以看到分为3段,每段就是上面的一部分数据。
[什么是 JWT -- JSON WEB TOKEN](https://www.jianshu.com/p/576dbf44b2ae)
[傻傻分不清之 Cookie、Session、Token、JWT](https://juejin.cn/post/6844904034181070861)
[JWT详细教程与使用](https://blog.csdn.net/Top_L398/article/details/109361680)
# 2.编写JWT工具
我们会用到比较流行的java语言的JWT工具,jjw
## 2.1.添加JWT依赖
我们需要先在项目中引入JWT依赖:
```xml
io.jsonwebtoken
jjwt-api
0.11.2
io.jsonwebtoken
jjwt-impl
0.11.2
runtime
io.jsonwebtoken
jjwt-jackson
0.11.2
runtime
com.fasterxml.jackson.core
jackson-databind
joda-time
joda-time
com.fasterxml.jackson.core
jackson-databind
org.projectlombok
lombok
joda-time
joda-time
org.springframework.boot
spring-boot-starter-data-redis
```
## 2.2.载荷对象
JWT中,会保存载荷数据,我们计划存储2部分:
- jti:jwt的id
- UserDetail:用户数据
为了方便后期获取,我们定义一个类来封装。
添加一个实体类,代表载荷信息
```java
import lombok.Data;
//载荷对象
@Data
public class Payload {
/**
* tocken的id
*/
private String jti;
/**
* 用户数据
*/
private UserDetail userDetail;
}
```
载荷中的UserDetail信息,也需要一个实体类表示,这里我们定义一个UserDetail类。
这里我们假设用户信息包含2部分:
- id:用户id
- username:用户名
```java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class UserDetail {
/**
* 用户id
*/
private Long id;
/**
* 用户名
*/
private String username;
}
```
## 2.3.工具
我创建一个JwtUtils 工具类,用来封装几个方法:
- createJwt() :生成JWT
- parseJwt() :验证并解析JWT
```java
import com.example.jwt.constants.RedisConstants;
import com.example.jwt.dto.Payload;
import com.example.jwt.dto.UserDetail;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class JwtUtils {
/**
* JWT解析器
*/
private final JwtParser jwtParser;
/**
* 秘钥
*/
private final SecretKey secretKey;
// @Autowired
private StringRedisTemplate redisTemplate;
private final static ObjectMapper mapper = new ObjectMapper();
public JwtUtils(String key) {
// 生成秘钥
secretKey = Keys.hmacShaKeyFor(key.getBytes(Charset.forName("UTF-8")));
// JWT解析器
this.jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
}
/**
* 生成jwt,自己指定的JTI
*
* @param userDetails 用户信息
* @return JWT
*/
public String createJwt(UserDetail userDetails) {
try {
// 生成tokenid
String jti=createJti();
//存入redis中
// this.redisTemplate.opsForValue().set(RedisConstants.JTI_KEY_PREFIX+userDetails.getUsername(),jti,30, TimeUnit.MINUTES);
return Jwts.builder().signWith(secretKey)
.setId(jti)
.claim("user", mapper.writeValueAsString(userDetails))
.compact();
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* 解析jwt,并将用户信息转为指定的Clazz类型
*
* @param jwt token
* @return 载荷,包含JTI和用户信息
*/
public Payload parseJwt(String jwt) {
try {
Jws claimsJws = jwtParser.parseClaimsJws(jwt);
Claims claims = claimsJws.getBody();
Payload payload = new Payload();
payload.setJti(claims.getId());
payload.setUserDetail(mapper.readValue(claims.get("user", String.class), UserDetail.class));
return payload;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String createJti() {
return StringUtils.replace(UUID.randomUUID().toString(), "-", "");
}
/**
* 刷新jwt有效期
* @param username
*/
public void refreshJwt(String username) {
String key= RedisConstants.JTI_KEY_PREFIX+username;
//重置key的过期时间
redisTemplate.expire(key,30,TimeUnit.MINUTES);
}
}
```

## 2.4.测试
### 2.4.1.配置秘钥
在`application.yml`文件中配置秘钥:
```yaml
yy:
jwt:
key: helloWorldJavaAuthServiceSecretKe
```
定义一个配置类,注册`JwtUtils`注入到Spring的容器。
```java
import com.example.jwt.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JwtConfig {
@Value("${yy.jwt.key}")
private String key;
@Bean
public JwtUtils jwtUtils(){
return new JwtUtils(key);
}
}
```
### 2.4.2.测试类
```java
@Autowired
private JwtUtils jwtUtils;
@Test
public void test() {
// 生成jwt
String jwt = jwtUtils.createJwt(UserDetail.of(112L, "lele"));
System.out.println("jwt = " + jwt);
// 解析jwt
Payload payload = jwtUtils.parseJwt(jwt);
System.out.println("payload = " + payload);
}
```
结果:
```
jwt = eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyZTRkMGI1NjZiODY0YjUzODAwZTI3NGNhOWE0MTcxYSIsInVzZXIiOiJ7XCJpZFwiOjExMixcInVzZXJuYW1lXCI6XCJsZWxlXCJ9In0.NGa42tISwsLg_hyONasdGPGDigFFxkWbH04wd4ELztY
payload = Payload(jti=2e4d0b566b864b53800e274ca9a4171a, userDetail=UserDetail(id=112, username=lele))
```