JWT
全称为JSON Web Token
,即JSON Web
令牌,用于前端和后端之间以JSON
对象安全的传递数据,提供了多种数字加密的算法,通过JSON
作为Web
中的令牌,通常用来做安全校验、授权
在传统的方式中,通常使用session
进行验证,首次登陆时将认证信息放到session
中,即在客户端和服务器中都保存一份登录的信息,下次访问时客户端将登录信息发送到服务器,服务器根据登录信息进行验证
问题:一般认证的session
为了相应的速度,在服务器进行存储时都是存储在缓存中(Redis
等),随着用户量变大,会造成服务器的内存压力变大,基于cookie
的认证都可以进行伪造请求,从而达到攻击的目的
JWT
的令牌只存储在客户端而不存储在服务端
在这个令牌中包含了一些用户信息,避免了session
信息的内存占用
构成
令牌分为三部分,每个部分可以看作是一个JSON
对象:
-
标头
header
-
由两部分组成,分别是令牌的类型(
jwt
)和签名的算法,HS256
是默认并且推荐使用的签名算法-
对应的
json
为{ "typ": "jwt", "alg": "HS256等签名算法" }
-
为了保证不被一眼看出来,会将标头使用
Base64
进行编码,编码的结果是一串字符串,使用此格式进行的编码可以做一个解码,所以Base64
不能称为加密
-
-
-
有效载荷
payload
- 其中包含着声明,通常是用户信息或者其他的数据声明同样也会采用
Base64
进行编码 - 例如可以把用户名、角色、id等用到的信息放入到这一部分
- 尽量不要放敏感信息,例如密码等
- 其中包含着声明,通常是用户信息或者其他的数据声明同样也会采用
-
签名
signature
,读音为ˈsɪɡnətʃər
- 是将
header的Base64编码.payload的Base64编码
的字符串加上salt
之后,经过HS256
等指定的签名算法进行加密的生成的 salt
永远只能自己知道,通过这个salt
验证签名的合法性,而这个salt
可以看作是一个私钥,通过header
和payload
取出来加上salt
使用签名算法进行加密后的结果可以看作是一个公钥- 后端要进行验签,即将
header
和payload
取出来加上salt
使用签名算法进行加密和第三部分进行对比,只要有一个部分更改了,验证都会失败
- 是将
由这三部分组成一个字符串,中间用.
作为分隔header.payload.signature
整合到Java
所支持的依赖列表
引入依赖
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
生成Token
:
-
String token = JWT.create() .withClaim("username", "admin") // 放入到payload中的值,可以有多个 .withClaim("isAdmin", true) .withExpiresAt(calendar.getTime()) // 设置过期时间 .sign(Algorithm.HMAC256("Salt")); // 签名算法,需要包含salt
claim
中文为宣称、声明,读音为kleɪm
expire
中文为失效,终止,读音为ɪkˈspaɪər
通过Token
和salt
验证令牌:
-
如果
token
过期或者验证失败,将会在第4
行的代码中抛出异常 -
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("salt")) .build(); //可以得到解码后的内容,如果解码失败,将会抛出异常 DecodedJWT decodedJWT = verifier.verify("token");// 填入token System.out.println("decodedJWT.getExpiresAt() = " + decodedJWT.getExpiresAt()); decodedJWT.getClaims().forEach((k, v) -> { System.out.println("key = " + k + ", value = " + v); }); System.out.println("decodedJWT.getPayload() = " + decodedJWT.getPayload()); System.out.println("decodedJWT.getSubject() = " + decodedJWT.getSubject()); System.out.println("decodedJWT.getHeader() = " + decodedJWT.getHeader()); System.out.println("decodedJWT.getToken() = " + decodedJWT.getToken());
verifier
中文为验证器,读音为ˈvɛrəˌfaɪər
verify
中文为验证,读音为ˈverɪfaɪ
-
也可以不验证,直接进行取出值
DecodedJWT decode = JWT.decode("eyJ0eXAiOiJKV1QiLCJpc1VzZXIiOnRydWUsImFsZyI6IkhTMjU2IiwidXNlcm5hbWUiOiJnb29kIn0.eyJleHAiOjE2NTMyMTE4MDd9.cWSuHHlwvY_T9jDAwhoREc5mn8wz36c9TOZNgCTNmWs"); decode .getClaims() .forEach((k, v) -> { System.out.println("key = " + k + ", value = " + v); }); System.out.println("decode.getPayload() = " + decode.getPayload()); System.out.println("decode.getHeader() = " + decode.getHeader()); System.out.println("decode.getExpiresAt() = " + decode.getExpiresAt()); System.out.println("decode.getSignature() = " + decode.getSignature());
-
key
的类型为String
,value
的类型为JsonNodeClaim
-
对于每个
value
,可以通过as类型
的方式取出相应类型的值
封装工具类
- 生成
token
- 验证
token
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
public class TokenUtil {
private static final String SECRET = "afjk##UHGBHJ8894@/*-*-+";
private TokenUtil() {
}
public static String getToken(Map<String, ?> payload) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 3);
return JWT.create()
.withPayload(payload)
.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256(SECRET));
}
public static DecodedJWT verifyToken(String token) {
return JWT.require(Algorithm.HMAC256(SECRET))
.build()
.verify(token);
}
}
也可以整合SpringBoot
,配合拦截器进行使用:
通常会将token
放到请求头中
Q.E.D.