【Spring-Security】Re12 JsonWebToken
一、认证机制种类:
1、HTTP-Basic-Auth
每次请求接口必须提供账号信息【username + password】
但是信息有暴露风险,配合RestFul风格使用,逐渐淘汰
2、Cookie-Auth
首次请求在客户端和服务端分别创建Cookie + Session 对象
通过两者的对象匹配实现状态管理,浏览器关闭会让Cookie对象销毁
可以设置Cookie过期时间
3、Open-Authorization [ Oauth ]
开放授权,授权给第三方应用来访问服务资源
4、Token-Auth
基于令牌的验证,所有权限控制的判断全部以令牌为凭证通行访问
二、Json Web Token [ JWT ]
标准描述:
https://tools.ietf.org/html/rfc7519
官网地址:
https://jwt.io/
以前后端数据交互标准的JSON作为传输载体实现Token-Auth
https://www.bilibili.com/video/BV12D4y1U7D8?p=39
三、Java - JWT
创建一个SpringBoot项目。
需要Web组件和JJWT组件两个坐标:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
1、创建JWT
JWT令牌生成测试:
package cn.zeal4j; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.Date; @SpringBootTest class JJwtApplicationTests { @Test void contextLoads() { creatTokenTest(); } void creatTokenTest() { JwtBuilder jwtBuilder = Jwts.builder(); jwtBuilder. setId("8848"). // ID标识 setSubject("userL8"). // 用户主体 setIssuedAt(new Date()). // 签发时间 signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐 // 生成的JWT令牌 String jwtToken = jwtBuilder.compact(); System.out.println(jwtToken); } }
打印的令牌字符串:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg1OTk5fQ.BM0CHf0kawFz6uTBjcF8aeFDYdX0M4CN0PswEPm9W0U
令牌使用点号分割令牌的各个信息
eyJhbGciOiJIUzI1NiJ9 # 头部信息
.
eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg1OTk5fQ # 荷载信息
.
BM0CHf0kawFz6uTBjcF8aeFDYdX0M4CN0PswEPm9W0U # 签发信息
2、解析JWT
解密处理:
package cn.zeal4j; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.Base64Codec; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import sun.misc.BASE64Decoder; import java.util.Date; @SpringBootTest class JJwtApplicationTests { @Test void contextLoads() { creatTokenTest(); } void creatTokenTest() { JwtBuilder jwtBuilder = Jwts.builder(); jwtBuilder. setId("8848"). // ID标识 setSubject("userL8"). // 用户主体 setIssuedAt(new Date()). // 签发时间 signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐 // 生成的JWT令牌 String jwtToken = jwtBuilder.compact(); System.out.println(jwtToken); System.out.println("- - - - - JWT-Token-Decoder!!! - - - -"); String[] tokenParts = jwtToken.split("\\."); String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]); String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]); String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]); System.out.println("tokenHead -> " + tokenHead); System.out.println("tokenCarrier -> " + tokenCarrier); System.out.println("tokenSignature -> " + tokenSignature); } }
输出结果:
- - - - - JWT-Token-Decoder!!! - - - - tokenHead -> {"alg":"HS256"} tokenCarrier -> {"jti":"8848","sub":"userL8","iat":1601386514 tokenSignature -> *�2ɥr�ԻjNz�4�����RzЂ97�+f
签名是被盐加密过了的,所以就算用算法解密了,但是有盐打乱,还是不能知道原文是什么
另外每次生成的Token信息的也会有不同的区别:
eyJhbGciOiJIUzI1NiJ9. eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NTE0fQ. KuQyyaVys9S7_ak56pTSWkJbtotxSetCCOTeeBitmmw - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eyJhbGciOiJIUzI1NiJ9. eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ. LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0
头密文是一直的,但是荷载密文是在结尾有区别,原因是我们加了签发日期
而下面的签发密文是盐加密的,每次都会算出不一样的密文结果
package cn.zeal4j; import io.jsonwebtoken.*; import io.jsonwebtoken.impl.Base64Codec; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import sun.misc.BASE64Decoder; import java.util.Date; @SpringBootTest class JJwtApplicationTests { @Test void contextLoads() { // creatTokenTest(); parseJwtTokenTest(); } void creatTokenTest() { JwtBuilder jwtBuilder = Jwts.builder(); jwtBuilder. setId("8848"). // ID标识 setSubject("userL8"). // 用户主体 setIssuedAt(new Date()). // 签发时间 signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐 // 生成的JWT令牌 String jwtToken = jwtBuilder.compact(); System.out.println(jwtToken); System.out.println("- - - - - JWT-Token-Decoder!!! - - - -"); String[] tokenParts = jwtToken.split("\\."); String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]); String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]); String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]); System.out.println("tokenHead -> " + tokenHead); System.out.println("tokenCarrier -> " + tokenCarrier); System.out.println("tokenSignature -> " + tokenSignature); } void parseJwtTokenTest() { // 模拟客户端发送的JWT令牌 final String simulationClientJwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ.LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0"; Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(simulationClientJwtToken); JwsHeader header = claimsJws.getHeader(); String keyId = header.getKeyId(); // 获取ID标识 // 负载对象 Claims body = claimsJws.getBody(); String subject = body.getSubject(); // 获取签发主体 Date issuedAt = body.getIssuedAt(); // 获取签发时间 String id = body.getId(); // 这也能获取ID? String signature = claimsJws.getSignature(); // 签名 System.out.println("from JwsHeader KeyId -> " + keyId); System.out.println("from ClaimsBody id -> " + id); System.out.println("ClaimsBody subject -> " + subject); System.out.println("ClaimsBody issuedAt -> " + issuedAt); System.out.println("signature -> " + signature); } }
打印结果:
from JwsHeader KeyId -> null from ClaimsBody id -> 8848 ClaimsBody subject -> userL8 ClaimsBody issuedAt -> Tue Sep 29 21:39:16 CST 2020 signature -> LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0
3、Token过期时间设置
package cn.zeal4j; import io.jsonwebtoken.*; import io.jsonwebtoken.impl.Base64Codec; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import sun.misc.BASE64Decoder; import java.text.SimpleDateFormat; import java.util.Date; @SpringBootTest class JJwtApplicationTests { @Test void contextLoads() { // 1、creatTokenTest(); // 2、parseJwtTokenTest(); // 3、parseExpiredJwtTokenTest(creatTokenTest()); parseExpiredJwtTokenTest(creatTokenTest()); } String creatTokenTest() { long currentTimeMillis = System.currentTimeMillis(); long theExpireTimeMills = currentTimeMillis + (60 * 1000); // 1毫秒 * 1000(1秒) * 60 = 1 分钟 JwtBuilder jwtBuilder = Jwts.builder(); jwtBuilder. setExpiration(new Date(theExpireTimeMills)). setId("8848"). // ID标识 setSubject("userL8"). // 用户主体 setIssuedAt(new Date()). // 签发时间 signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐 // 生成的JWT令牌 String jwtToken = jwtBuilder.compact(); System.out.println(jwtToken); System.out.println("- - - - - JWT-Token-Decoder!!! - - - -"); String[] tokenParts = jwtToken.split("\\."); String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]); String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]); String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]); System.out.println("tokenHead -> " + tokenHead); System.out.println("tokenCarrier -> " + tokenCarrier); System.out.println("tokenSignature -> " + tokenSignature); return jwtToken; } void parseJwtTokenTest() { // 模拟客户端发送的JWT令牌 final String simulationClientJwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ.LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0"; Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(simulationClientJwtToken); JwsHeader header = claimsJws.getHeader(); String keyId = header.getKeyId(); // 获取ID标识 // 负载对象 Claims body = claimsJws.getBody(); String subject = body.getSubject(); // 获取签发主体 Date issuedAt = body.getIssuedAt(); // 获取签发时间 String id = body.getId(); // 这也能获取ID? String signature = claimsJws.getSignature(); // 签名 System.out.println("from JwsHeader KeyId -> " + keyId); System.out.println("from ClaimsBody id -> " + id); System.out.println("ClaimsBody subject -> " + subject); System.out.println("ClaimsBody issuedAt -> " + issuedAt); System.out.println("signature -> " + signature); } void parseExpiredJwtTokenTest(String jwtToken) { Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(jwtToken); // eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDEzODgzODYsImp0aSI6Ijg4NDgiLCJzdWIiOiJ1c2VyTDgiLCJpYXQiOjE2MDEzODgzMjZ9.Ve0n_TylzsCFHnk4vrjWKM_fZPGteupsx2aLbJU2E0k JwsHeader header = claimsJws.getHeader(); String keyId = header.getKeyId(); // 获取ID标识 // 负载对象 Claims body = claimsJws.getBody(); String subject = body.getSubject(); // 获取签发主体 Date issuedAt = body.getIssuedAt(); // 获取签发时间 String id = body.getId(); // 这也能获取ID? String signature = claimsJws.getSignature(); // 签名 // System.out.println("from JwsHeader KeyId -> " + keyId); // System.out.println("from ClaimsBody id -> " + id); // // System.out.println("ClaimsBody subject -> " + subject); // System.out.println("ClaimsBody issuedAt -> " + issuedAt); // System.out.println("signature -> " + signature); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String signTime = simpleDateFormat.format(body.getIssuedAt()); String expireTime = simpleDateFormat.format(body.getExpiration()); String thisTime = simpleDateFormat.format(new Date()); System.out.println("- - - JwtTokenTimeExpireTest - - -"); System.out.println("签发时间 -> " + signTime); System.out.println("过期时间 -> " + expireTime); System.out.println("现在时间 -> " + thisTime); } }
结果打印:
- - - JwtTokenTimeExpireTest - - - 签发时间 -> 2020-09-29 22:05:26 过期时间 -> 2020-09-29 22:06:26 现在时间 -> 2020-09-29 22:05:26
超过过期时间解析Token会让程序抛出TokenExpireException异常,详细信息自测,不赘述了
4、自定义申明:
String creatTokenTest() { long currentTimeMillis = System.currentTimeMillis(); long theExpireTimeMills = currentTimeMillis + (60 * 1000); // 1毫秒 * 1000(1秒) * 60 = 1 分钟 Map<String, Object> map = new HashMap<>(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); // ...... JwtBuilder jwtBuilder = Jwts.builder(); jwtBuilder. setExpiration(new Date(theExpireTimeMills)). setId("8848"). // ID标识 setSubject("userL8"). // 用户主体 setIssuedAt(new Date()). // 签发时间 claim("customClaimKey01", "customClaimValue01"). // 自定义申明方式一 claim("customClaimKey02", "customClaimValue02"). addClaims(map). // 自定义申明方式二 signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐 // 生成的JWT令牌 String jwtToken = jwtBuilder.compact(); System.out.println(jwtToken); System.out.println("- - - - - JWT-Token-Decoder!!! - - - -"); String[] tokenParts = jwtToken.split("\\."); String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]); String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]); String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]); System.out.println("tokenHead -> " + tokenHead); System.out.println("tokenCarrier -> " + tokenCarrier); System.out.println("tokenSignature -> " + tokenSignature); return jwtToken; } void parseJwtTokenTest() { // 模拟客户端发送的JWT令牌 final String simulationClientJwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ.LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0"; Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(simulationClientJwtToken); JwsHeader header = claimsJws.getHeader(); String keyId = header.getKeyId(); // 获取ID标识 // 负载对象 Claims body = claimsJws.getBody(); String subject = body.getSubject(); // 获取签发主体 Date issuedAt = body.getIssuedAt(); // 获取签发时间 String id = body.getId(); // 这也能获取ID? Object o = body.get("key-name"); // 获取申明 String signature = claimsJws.getSignature(); // 签名 System.out.println("from JwsHeader KeyId -> " + keyId); System.out.println("from ClaimsBody id -> " + id); System.out.println("ClaimsBody subject -> " + subject); System.out.println("ClaimsBody issuedAt -> " + issuedAt); System.out.println("signature -> " + signature); }