JWT教程
官方地址:https://jwt.io
What is JSON Web Token?
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA. —[摘自官网]
什么是JSON Web Token?
jsonwebtoken(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名
什么时候使用JWT
-
授权
这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
-
信息交换
JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。
JWT的结构是什么样的?
在其紧凑的形式中,JWT由三部分组成,由点(
.
)分隔,分别是:
- 标头(Header)
- 有效载荷(PayLoad)
- 签名(Signature)
因此,JWT通常用下面的形式展示: xxxxx.yyyyy.zzzzz
- 标头(Header)
标头通常由两部分组成:令牌的类型,即 JWT,以及正在使用的签名算法,例如 HMAC SHA256 或 RSA。
{
"alg": "HS256",
"typ": "JWT"
}
这个 JSON 被Base64Url编码以形成 JWT 的第一部分。该信息仅仅使用了base编码,所以任何人都可以解码获取到原信息。
- 有效载荷(PayLoad)
令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和附加数据的陈述。声明分为三种类型:注册声明、公开声明和私人声明。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
有效载荷是Base64Url编码形成JSON Web令牌的第二部分。该信息仅仅使用了base编码,所以任何人都可以解码获取到原信息,所以不要将敏感信息放到里面。
- 签名(Signature)
要创建签名部分,您必须获取编码的标头(Header)、编码的有效载荷(PayLoad)以及一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
JWT签名生成?
jwt在线生成网址为:https://jwt.io/
JWT优势是什么?
-
简洁
由于 JSON 不像 XML 那样冗长,因此在对其进行编码时,它的大小也更小,这使得 JWT 比 SAML 更紧凑。这使得 JWT 成为在 HTML 和 HTTP 环境中传递的不错选择。
-
安全
SWT 只能通过使用 HMAC 算法的共享密钥进行对称签名。但是,JWT 和 SAML 令牌可以使用 X.509 证书形式的公钥/私钥对进行签名。与签署 JSON 的简单性相比,使用 XML 数字签名签署 XML 而不引入隐蔽的安全漏洞是非常困难的。
-
跨语言
因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
-
适用于分布式微服
不需要在服务端保存会话信息,特别适用于分布式微服务。
快速上手
-
引入依赖
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.19.2</version> </dependency>
-
使用HS256生成token
try { Algorithm algorithm = Algorithm.HMAC256("secret"); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); //输出令牌 System.out.println(token); } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }
-
token验证
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { Algorithm algorithm = Algorithm.HMAC256("secret"); //use more secure key JWTVerifier verifier = JWT.require(algorithm) .withIssuer("auth0") .build(); //Reusable verifier instance DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException exception){ //Invalid signature/claims }
-
常见的异常信息
AlgorithmMismatchException 算法不匹配异常
InvalidClaimException 失效的payload异常
JWTCreationException jwt创建异常
JWTDecodeException jwt解码异常
JWTVerificationException jwt验证异常
SignatureGenerationException 签名生成异常
SignatureVerificationException 签名不一致异常
TokenExpiredException 令牌过期异常
工具类封装
public final class JwtToken {
/*
*
* JWT的参数说明:
* 1.withClaim("iss", "Service") 自定义属性,有效负载
* 2.withNotBefore(new Date()) 生效时间
* 3.withJWTId(UUID.randomUUID().toString()) 保证唯一
* 4.withIssuer(ISSUER) 发布者
* 5.withExpiresAt(expiresAt) 闲置(超期)时间
* 6.withIssuedAt(iatDate) 签名时间
* 7.sign(algorithm) 签名
*/
/**
* 生成令牌token,默认无失效时间
* 符合JWT标准,采用HMAC256加密
*
* @return 令牌字符串 格式:<br/>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqaXQuY29tLmNuIiwiZXhwIjoxNTg2MzkzOTk5fQ.5qGSr47xYj5UxTU7iIb-VIqc0QDLBPEPfiR11wedWqE
* @throws IllegalArgumentException 参数异常
* @throws JWTCreationException Token生成异常
*/
public static String generateToken() throws JWTCreationException {
//生成令牌token
return generateToken(null);
}
/**
* 生成令牌token,允许自定义闲置(超期)时间
* 符合JWT标准,采用HMAC256加密
*
* @param expiresAt 令牌闲置(超期)时间,java.util.Date类型,不建议设置过长
* @return Token字符串 格式:<br/>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqaXQuY29tLmNuIiwiZXhwIjoxNTg2MzkzOTk5fQ.5qGSr47xYj5UxTU7iIb-VIqc0QDLBPEPfiR11wedWqE
* @throws JWTDecodeException Token生成异常
*/
public static String generateToken(Date expiresAt) throws JWTCreationException {
/*
* 使用第三方包java-jwt工具包创建
* 使用我方密钥通过HMAC256算法生成签名,用于验证是否被篡改的依据
*/
//获取签名算法密钥
Algorithm algorithm = getAlgorithm();
//创建token
JWTCreator.Builder jwtCreatorBuilder = JWT.create()
// 生效时间
.withNotBefore(new Date())
// 保证唯一
.withJWTId(UUID.randomUUID().toString())
// 发布者
.withIssuer(ISSUER);
// 如果有自定义时间,则设置token的超期时间
if(expiresAt != null){
/*
* 超期时间
*
* 注意:
* 1.JWT的token的过期时间是在生成token的时候设置的,无法修改,要修改只能在生成token的时候修改
* 2.不能把withExpiresAt当成可伸缩的存活时间,主要使用场景
*/
jwtCreatorBuilder = jwtCreatorBuilder.withExpiresAt(expiresAt);
}
//返回生成的token
return jwtCreatorBuilder.sign(algorithm);
}
/**
* Token验证,当Token出现异常或超期时抛出JWTVerificationException异常
*
* @param token Token字符串,格式:<br/>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqaXQuY29tLmNuIiwiZXhwIjoxNTg2MzkzOTk5fQ.5qGSr47xYj5UxTU7iIb-VIqc0QDLBPEPfiR11wedWqE
* @return 返回DecodedJWT对象,对象中可获取Token内容
* @throws JWTVerificationException Token验证未通过抛出异常,业务模块应捕获异常进行判断Token有效性
*/
public static DecodedJWT verifyToken(String token) throws JWTVerificationException {
// 使用第三方包java-jwt工具包创建
// 使用我方密钥通过HMAC256算法生成签名,用于验证是否被篡改的依据
//获取签名算法密钥
Algorithm algorithm = getAlgorithm();
//生成需要验证的token的信息
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
//验证token签名,如果验证未通过跑出异常
return verifier.verify(token);
}
/**
* 生成token 添加自定义Claim值
*/
public static String generateToken(Date expiresAt, Map<String,String> ClaimMap){
//获取签名算法密钥
Algorithm algorithm = getAlgorithm();
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload
ClaimMap.forEach((k,v)->{
builder.withClaim(k,v);
});
//创建token
JWTCreator.Builder jwtCreatorBuilder = builder
// 生效时间
.withNotBefore(new Date())
// 保证唯一
.withJWTId(UUID.randomUUID().toString())
// 发布者
.withIssuer(ISSUER);
// 如果有自定义时间,则设置token的超期时间
if(expiresAt != null){
/*
* 超期时间
*
* 注意:
* 1.JWT的token的过期时间是在生成token的时候设置的,无法修改,要修改只能在生成token的时候修改
* 2.不能把withExpiresAt当成可伸缩的存活时间,主要使用场景
*/
jwtCreatorBuilder = jwtCreatorBuilder.withExpiresAt(expiresAt);
}
//返回生成的token
return jwtCreatorBuilder.sign(algorithm);
}
/**
* 生成token的签名算法密钥
* @return 返回token的签名算法密钥
*/
private static Algorithm getAlgorithm(){
return Algorithm.HMAC256(SECRET);
}
/**
* Token 密钥
*/
private final static String SECRET = "test";
/**
* Token 发布机构
*/
private final static String ISSUER = "test";
}