JWT解读与使用
JWT解读与使用
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。它具有简洁、自包含和可验证的特点,广泛应用于Web应用的身份验证和信息交换场景。本文将从JWT的基本概念、使用场景、结构组成、使用方式、安全问题以及在Spring Boot中的集成实践等方面进行详细解读。
什么是JWT?
JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对jwt进行签名。
虽然jwt也可以加密以在各方之间提供保密性,但我们将重点关注已签名的令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方是对其进行签名的一方。
什么时候使用JWT?
授权
授权:这是使用JWT最常见的场景。用户登录后,每个后续请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前广泛使用JWT的一个特性,因为它的开销很小,并且能够轻松地跨不同的域使用。
信息交换
信息交换:JSON Web令牌是各方之间安全传输信息的好方法。因为可以对jwt进行签名(例如,使用公钥/私钥对),所以可以确保发送者就是他们所说的那个人。此外,由于签名是使用报头和有效负载计算的,因此您还可以验证内容是否未被篡改。
为什么使用JWT?
JWT 和 传统 Token 的区别:
JWT结构
JSON Web令牌的简洁形式由点(.)分隔的三部分组成,它们是:
.Header
.Payload
.Signature
因此,JWT通常如下所示:
xxxx.yyyyy.zzzzz(
Header
.
Payload.Signature
)
Header
报头通常由两部分组成:令牌的类型(JWT)和使用的签名算法(如HMAC SHA256或RSA)。
{
"alg": "HS256",
"typ": "JWT"
}
然后,对该JSON进行Base64Url编码,以形成JWT的第一部分。
Payload
令牌的第二部分是有效负载,它包含声明。声明是关于实体(通常是用户)和附加数据的陈述。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后,对有效负载进行Base64Url编码,以形成JSON Web令牌的第二部分。
注意:JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
JWT使用方式
在身份验证中,当用户使用其凭据成功登录时,将返回一个JWT。由于令牌是凭证,因此必须非常小心地防止安全问题。一般来说,您不应该保留令牌超过所需的时间。
由于缺乏安全性,您也不应该将敏感的会话数据存储在浏览器存储中。
每当用户想要访问受保护的路由或资源时,用户代理应该发送JWT,通常在Authorization头中使用承载模式。头文件的内容应该如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是一种无状态授权机制。服务器受保护的路由将检查Authorization报头中的有效JWT,如果存在,则允许用户访问受保护的资源。如果JWT包含必要的数据,那么为某些操作查询数据库的需求可能会减少,尽管情况并非总是如此。
注意,如果通过HTTP头发送JWT令牌,则应该尽量防止它们变得太大。有些服务器不接受超过8 KB的报头。如果您试图在JWT令牌中嵌入太多信息,比如包含所有用户的权限,那么您可能需要一个替代解决方案,比如Auth0细粒度授权。
如果令牌是在授权头中发送的,那么跨域资源共享(CORS)就不会出现问题,因为它不使用cookie。
下图显示了如何获得JWT并使用JWT访问api或资源:
- 应用程序或客户端向授权服务器请求授权
- 授予授权后,授权服务器将向应用程序返回一个访问令牌。
- 应用程序使用访问令牌访问受保护的资源(如API)。
JWT安全问题
空加密算法:指的是没有使用任何加密算法的JWT,这在实际应用中是不常见的,因为加密是保障信息安全的标准做法。然而,确实存在一些JWT的实现可能不会使用加密,尤其是在一些对安全要求不是非常高的场景下。 通过空加密算法,我们主要了解 JWT 前两部分的组成方式及利用手法,简单来说就是将 header 部分的 alg 修改为 none ,然后在 signature 部分制空即可达成不安全的 JWT 加密算法的效果。
放入解码平台进行解码,可以得出前两部分的内容:
随后我们对数据包进行修改,首先将 alg 中加密方式设置为 none , 然后将 admin 中的 false 修改为 true ,重新进行 Base64 编码,得到如下的 JWT 进行请求发送并且成功。
注意,虽然 signature 部分为空,但是仍然需要之前加上 . 号达成格式匹配,此时将其设置为 access_token 进行重放,发现正常通过。
springboot集成JWT
- 添加依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
- 数据加密
@PostMapping("/login")
public Map<String, Object> login() {
Map<String, Object> map = new HashMap<>();
String token = JWTUtil.sign("chenq");
if (token != null) {
map.put("code", "200");
map.put("message", "认证成功");
map.put("token", token);
return map;
}
map.put("code", "403");
map.put("message", "认证失败");
return map;
}
public static final long EXPIRE_TIME = 1000 * 60 * 60 * 24;
public static final String secret = "qwer1234567890!@#$%&*12345";
public static String sign(String username) {
// 现在系统的时间 + 一天
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
// 对密码进行加密
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
}
- 数据解密
/**
* token解密
* @param request
* @return
*/
public static String getUserNameByToken(HttpServletRequest request) {
String token = request.getHeader("token");
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username")
.asString();
}
- token验证
/**
* 检验Token是否正确
* @param token
* @param username
* @return
*/
public static boolean verify(String token, String username) {
try {
// 设置加密算法
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
// 效验TOKEN
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
总结
使用 JWT 令牌的最佳位置是在服务器到服务器之间的通信。
建议:
- 修复算法,不允许客户端切换算法。
- 在使用对称密钥对令牌进行签名时,请确保使用适当的密钥长度。
- 确保添加到令牌的声明不包含个人信息。如果需要添加更多信息,请同时选择加密令牌。
- 向项目添加足够的测试用例,以验证无效令牌是否确实不起作用。与第三方集成以检查您的令牌并不意味着您根本没有测试您的应用程序。