「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」
JWT (Json web token)概述
JWT的由来
由于http 协议本身是无状态的协议 这是所有问题的起点和关键,所以使用了session
但是传统的Session存在两个问题
- 用户请求规模大之后增加服务器内存开销;
- 不利于服务端搭建集群。
所以为了解决这个原因,就使用了JWT
JWT 的认证流程
- 客户端发送用户名和密码至服务端进行认证。
- 服务端认证通过后,生成一个具有唯一性标识的字符串(Token)
- 服务端将 Token 发还给客户端,客户端后续的请求都需要带上这个 Token
- 服务端再次收到客户端请求时,认证这个 Token 是否是自己当初生成且未经篡改过的。如果没毛病,那么该干嘛干嘛。
Jwt组成
-
头部(Header)
作用:用于描述这个Jwt的基本组成部分,如类型,加密方式等
{ "typ": "JWT", "alg": "HS256" } 真正加密后是这样的 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 复制代码 -
荷载(Payload)
作用:代表JWT的信息内容
最起码有五个标准字段,剩下的都是可以自己添加的信息
{ "iss": "John Wu JWT", "iat": 1441593502, "exp": 1441594722, "aud": "www.example.com", "sub": "jrocket@example.com", "from_user": "B", "target_user": "A" } 加密后是这样的 eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9 复制代码 -
签名(Signature)
作用:这个一般是用作校验使用的,也就是为了解决jwt安全性使用的
一般是使用头部+荷载+密钥转换成的
安全问题
头部和荷载是使用base64加密生成的,是对称加密
-
因为签名是有密钥生成的,所以客户端并不能仿照出一个对应的JWT字符串
-
所以,一旦客户端修改了荷载或者头部,发给服务端,服务端使用头部+荷载+密钥并不是对应的签名,就可以检测到客户端动了这个JWT
Java使用JWT
-
加上依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- jjwt 依赖于 java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> 复制代码 -
加上对应的工具类
package com.xja.util; import java.util.*; import com.auth0.jwt.*; import com.auth0.jwt.algorithms.Algorithm; import io.jsonwebtoken.*; import org.apache.commons.codec.binary.Base64; import java.util.*; public class JwtUtil { public static final String defaultBase64EncodedSecretKey = "badbabe"; public static final SignatureAlgorithm defaultSignatureAlgorithm = SignatureAlgorithm.HS256; // 生成签名是所使用的秘钥 private final String base64EncodedSecretKey; // 生成签名的时候所使用的加密算法 private final SignatureAlgorithm signatureAlgorithm; public JwtUtil() { this(defaultBase64EncodedSecretKey, defaultSignatureAlgorithm); } public JwtUtil(String secretKey, SignatureAlgorithm signatureAlgorithm) { this.base64EncodedSecretKey = Base64.encodeBase64String(secretKey.getBytes()); this.signatureAlgorithm = signatureAlgorithm; } /** * 生成 JWT Token 字符串 * * @param iss 签发人名称 * @param ttlSeconds jwt 过期时间(秒) * @param claims 额外添加到荷部分的信息。 * 例如可以添加用户名、用户ID、用户(加密前的)密码等信息 */ public String encode(String iss, long ttlSeconds, Map<String, Object> claims) { if (claims == null) { claims = new HashMap<>(); } // 签发时间(iat):荷载部分的标准字段之一 long nowMillis = System.currentTimeMillis(); // 下面就是在为payload添加各种标准声明和私有声明了 JwtBuilder builder = Jwts.builder() // 荷载部分的非标准字段/附加字段,一般写在标准的字段之前。 .setClaims(claims) // JWT ID(jti):荷载部分的标准字段之一,JWT 的唯一性标识,虽不强求,但尽量确保其唯一性。 .setId(UUID.randomUUID().toString()) // 签发时间(iat):荷载部分的标准字段之一,代表这个 JWT 的生成时间。 .setIssuedAt(new Date(nowMillis)) // 签发人(iss):荷载部分的标准字段之一,代表这个 JWT 的所有者。通常是 username、userid 这样具有用户代表性的内容。 .setSubject(iss) // 设置生成签名的算法和秘钥 .signWith(signatureAlgorithm, base64EncodedSecretKey); if (ttlSeconds>= 0) { long expMillis = nowMillis + ttlSeconds * 1000; // 过期时间(exp):荷载部分的标准字段之一,代表这个 JWT 的有效期。 builder.setExpiration(new Date(expMillis)); } return builder.compact(); } /** * JWT Token 由 头部 荷载部 和 签名部 三部分组成。签名部分是由加密算法生成,无法反向解密。 * 而 头部 和 荷载部分是由 Base64 编码算法生成,是可以反向反编码回原样的。 * 这也是为什么不要在 JWT Token 中放敏感数据的原因。 * * @param jwtToken 加密后的token * @return claims 返回荷载部分的键值对 */ public Claims decode(String jwtToken) { // 得到 DefaultJwtParser return Jwts.parser() // 设置签名的秘钥 .setSigningKey(base64EncodedSecretKey) // 设置需要解析的 jwt .parseClaimsJws(jwtToken) .getBody(); } /** * 校验 token * 在这里可以使用官方的校验,或, * 自定义校验规则,例如在 token 中携带密码,进行加密处理后和数据库中的加密密码比较。 * * @param jwtToken 被校验的 jwt Token */ public boolean isVerify(String jwtToken) { Algorithm algorithm = null; switch (signatureAlgorithm) { case HS256: algorithm = Algorithm.HMAC256(Base64.decodeBase64(base64EncodedSecretKey)); break; default: throw new RuntimeException("不支持该算法"); } JWTVerifier verifier = JWT.require(algorithm).build(); verifier.verify(jwtToken); // 校验不通过会抛出异常 // 合法判断标准: // 1. JWT 的头部和荷载部分内容没有被篡改过。 // 2. JWT 没有过期 return true; } public static void main(String[] args) throws InterruptedException { JwtUtil util = new JwtUtil("tom", SignatureAlgorithm.HS256); Map<String, Object> map = new HashMap<>(); map.put("username", "tom"); map.put("password", "123456"); map.put("age", 20); String jwtToken = util.encode("tom", 1, map); System.out.println(jwtToken); util.isVerify(jwtToken); System.out.println("合法"); Thread.sleep(2000); util.isVerify(jwtToken); /* util.decode(jwtToken).entrySet().forEach((entry) -> { System.out.println(entry.getKey() + ": " + entry.getValue()); }); */ } } 复制代码 -
修改登录功能
-
首先判断登录账号密码是否成功
不成功就返回不成功
成功就继续
-
创建一个JWT对象
对象中传入几个参数
-
用户名
-
多久过期
-
可以放在荷载中的其他字段的一个Map类型对象
-
-
将对象返回给前台
-




近期评论