JWT令牌/JwtToken
JWT代表JSON Web Token,它是一种用于在网络应用之间安全传输信息的开放标准(RFC 7519)。JWT令牌由三个部分组成,以点号(.)分隔:
- 头部(Header):包含令牌的类型(即"JWT")和所使用的签名算法(例如HMAC、RSA或ECDSA)。
- 有效载荷(Payload):也称为声明(Claims),包含有关令牌的信息,如用户身份、权限和其他元数据。
- 签名(Signature):使用密钥对头部和有效载荷进行签名,以验证令牌的完整性和真实性。
下面示例是使用相同key生成的2个JWT令牌串,
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDY3OTAxMjUsInVzZXJuYW1lIjoic29uZ2h1YSJ9.2SXhF6siH_VHg39e4Nr_XH3bfBJ_IQDs7zYqbuW27Tg
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzYWxlTmFtZSI6IuW8oOS4iSIsImV4cCI6MTcwNjc5MDEyNiwidXNlcm5hbWUiOiJ6aGFuZ3NhbiJ9.rOlJHe91_5tMkOkYs6otcBNanzyskK6oF8z1N8oAlNA
在这两个示例token串中
- 第一部分Header是相同的{"typ": "JWT", "alg": "HS256"},
- 第二部分payload是数据部分,是对原始键值对(一个或多个,可以反序列化为map或json对象)做base64编码得到的字符串。既然是base64编码,那么,payload串的长度与原json串的长度正相关,原json串越长(键值对越多),base64串越长。
- 第三部分是利用HS256算法和key对payload原串生成的数字签名(定长)。
通过JWT在线解码工具可以查看jwtoken中的header和payload。其中,payload里包含的一个名为exp的second时间戳(Unix时间戳(Unix timestamp)转换工具),表示的是这个token的过期时间。
JWT令牌具有以下特点:
- 自包含性:有效载荷中包含了令牌的所有必要信息,避免了服务器端存储会话信息的需求。
- 可扩展性:有效载荷可以包含自定义声明,使其适应各种用例和应用需求。
- 安全性:由于签名的存在,JWT令牌在传输过程中是可验证的,防止内容篡改和伪造。
JWT令牌通常用于身份验证和授权,在客户端和服务器之间进行安全的无状态通信。当用户进行身份验证后,服务器会生成JWT令牌并将其发送给客户端,客户端随后将其在后续请求中作为授权凭证发送给服务器。服务器验证JWT令牌的有效性和完整性,并使用其中的信息进行授权和身份验证。
由于JWT令牌是基于标准的开放协议,因此在不同的环境中可以广泛应用,例如Web应用程序、移动应用程序和API等。
JWT令牌如何保证安全性和防止篡改和伪造?
JWT令牌通过使用签名机制来保证其安全性,以防止篡改和伪造。以下是JWT令牌如何实现安全性的工作原理:
-
生成签名密钥:JWT令牌的签名是使用秘密密钥生成的。该密钥只在服务器端知道,用于验证令牌的真实性。
-
创建令牌:当服务器生成JWT令牌时,它将头部和有效载荷进行Base64编码,并使用签名密钥进行签名。生成的签名将作为令牌的第三部分。
-
传输令牌:服务器将JWT令牌发送给客户端,并客户端在后续请求中将其作为授权凭证发送回服务器。
-
验证令牌:服务器在接收到JWT令牌后,首先检查令牌的完整性。它将再次使用相同的签名密钥对接收到的头部和有效载荷进行签名,并将生成的签名与令牌中的签名进行比较。如果两者匹配,那么令牌是完整的且未被篡改的。
-
提取数据:如果令牌的签名验证通过,服务器可以提取有效载荷中的数据,并使用其中的信息进行授权和身份验证。
通过这个过程,JWT令牌可以保证其安全性和防止篡改和伪造。任何试图篡改有效载荷或修改签名的尝试都会被服务器检测到,因为签名无法匹配。同时,由于签名密钥只在服务器端存在,客户端无法生成有效的签名,从而确保了令牌的真实性。
重要的是要保护签名密钥的安全性。如果签名密钥泄露,攻击者可能会伪造有效的签名并篡改令牌。因此,密钥管理和保护是实现JWT令牌安全性的关键。
JwtUtil -生成Token / 验证Token / 获取payload中的claim
package jstudy.jwt; import cn.hutool.core.codec.Base64; import cn.hutool.core.lang.Assert; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.DecodedJWT; import lombok.extern.slf4j.Slf4j; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @Author Scott * @Date 2018-07-12 14:23 * @Desc JWT工具类 **/ @Slf4j public class JwtUtil { /** * 扩展token * * @param payloadMap * @param secret * @return */ public static String createToken(Map<String, String> payloadMap, String secret, Long ttlMillis) { JWTCreator.Builder builder = JWT.create(); payloadMap.forEach(builder::withClaim); Date expiresAt = new Date(System.currentTimeMillis() + ttlMillis); // Token过期时间 Algorithm algorithm = Algorithm.HMAC256(secret); return builder.withExpiresAt(expiresAt).sign(algorithm); } /** * 验证token有效及是否过期 * * @param token * @param secret * @return */ public static boolean verify(String token, String secret) { // 根据密码生成JWT效验器 Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm).build(); try { // 校验TOKEN : 优先校验签名,然后校验是否过期 DecodedJWT decodeJWT = verifier.verify(token); // //验证token是否过期 // Date expiresAt = decodeJWT.getExpiresAt(); // if (expiresAt.before(new Date())) { // log.warn("token校验失败,token已过期 -{}", token); // return false; // } return true; } // token验签失败异常-SignatureVerificationException:The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256 catch (SignatureVerificationException exception) { log.warn("token校验失败 -{}", exception.getMessage()); return false; } // token过期异常-TokenExpiredException:The Token has expired on Fri Feb 23 19:26:52 CST 2024. catch (TokenExpiredException e) { log.warn("token校验失败-已过期 -{}", e.getMessage()); return false; } // 如果入参token不是一个有效的JWTtoken,会抛出JWT解码异常 JWTDecodeException: The token was expected to have 3 parts, catch (JWTDecodeException e) { but got 1. log.warn("token校验失败-不是有效token -{}", e.getMessage()); throw new UserAuthException("token非法"); } } /** * 从token的payload里获取信息(无需secret解密也能获得) * * @return */ public static String getClaim(String token, String keyField) { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim(keyField).asString(); } /** * 直接利用base64解码可以解析出来JwtToken的header和payload * * @param token */ private static void decodeToken(String token) { String[] array = token.split("\\."); System.out.println("解析token的header-->" + Base64.decodeStr(array[0])); System.out.println("解析token的payload-->" + Base64.decodeStr(array[1])); // 也可以这样解析 DecodedJWT decodedJWT = JWT.decode(token); System.out.println("解析token的header-->" + Base64.decodeStr(decodedJWT.getHeader())); System.out.println("解析token的payload-->" + Base64.decodeStr(decodedJWT.getPayload())); } public static void main(String[] args) throws InterruptedException { String key = "username"; Map<String, String> stringStringMap = new HashMap<>(); stringStringMap.put(key, "songhua"); stringStringMap.put("loginAccount", "a@b.com"); String token = createToken(stringStringMap, "secret15210572383", TimeUnit.MILLISECONDS.toMillis(30)); System.out.println("token-->" + token); // decodeToken(token); boolean verify1 = verify(token, "secret15210572383--"); Assert.isFalse(verify1, "应是false"); // 测试token过期 Thread.sleep(1000); boolean verify2 = verify(token, "secret15210572383"); Assert.isFalse(verify2, "应是false"); String username = getClaim(token, key); System.out.println("token中存储的username=" + username); } }
本文设计物料:https://www.processon.com/view/link/637c88efe401fd60d4cfddb0
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/10999001.html