# 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)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性 生成的数据格式: ![在这里插入图片描述](https://img-blog.csdnimg.cn/67aa25cf9e814629be126ab0e1bc5471.png) 可以看到分为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); } } ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/949bb3e1e6b349ffbf32b649f8657c00.png) ## 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)) ```