JWT 原理与实践
一.JWT概述
1.1.什么是JWT
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。 此信息可以通过数字签名进行验证和信任。 JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
虽然JWT可以加密以在各方之间提供保密,但我们将专注于签名token。 签名token可以验证其中包含的声明的完整性,而加密token则隐藏其他方的声明。 当使用公钥/私钥对签名token时,签名还证明只有持有私钥的一方是签署它的一方。
2. 何时使用JWT?
-
授权:这是我们使用JWT最广泛的应用场景。一次用户登录,后续请求将会包含JWT,对于那些合法的token,允许用户连接路由,服务和资源。如今在单独签名上是一种使用JWT的广泛特征。因为他们开销很小并且可以在不通领域轻松使用。
-
信息交换:JSON Web Token是一种在各方面之间安全信息传输的好的方式 因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。 此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
3.JSON Web Token(JWT)由什么组成?
JSON Web Token由三部分组成,以 " . " 分割。
他们是:
Header
Payload
Signature
因此,一个JWT通常以下面这种形式出现。
xxxxx.yyyyy.zzzzz
3.1.Header
标头通常由两部分组成:token的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。
For example:
{
"alg": "HS256",
"typ": "JWT"
}
然后,这个JSON被编码为Base64Url,形成JWT的第一部分。
3.2.Payload
token的第二部分是payload(有效负载),其中包含claims(声明)。Claims是关于一个实体(通常是用户)和其他数据类型的声名。claims有三种类型:registered,public,and private claims。
- 已注册的声明:这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行人),exp(到期时间),sub(主题),aud(观众)and others。(请注意,声明名称只有三个字符,因为JWT意味着紧凑。)
- 公开声明:这些可以由使用JWT的人随意定义。 但为避免冲突,应在IANA JSON Web Token Registry中定义它们,或者将其定义为包含防冲突命名空间的URI。
- 私人声明:这些声明是为了在同意使用它们的各方之间共享信息而创建的,并且既不是注册声明也不是公开声明。
An example payload could be:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
3.3.Signature(重点)
要创建签名部分,您必须采用编码header,编码的payload,a secret,标头中指定的算法,并对其进行签名。
for example如果你想使用HMAC SHA256算法,签名会以下面的方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的token的情况下,它还可以验证JWT的发件人是否是它所声称的人
好了以上部分都是官方对JWT的介绍,下面来看看身份认证使用session、直接使用token以及JWT的过程
3.4. 身份认证使用session、直接使用token以及JWT的流程
3.4.1.基于session身份认证的方案
3.4.2.基于token份认证的方案
3.4.3.基于JWT身份认证的方案
二.JWT 简单实用演示介绍
1.引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
我们通过三个方法来演示如何使用JWT
1.createToken() 创建不携带自定义信息的 token
2.createTokenWithClaim() 创建携带自定义信息的 token
3.verifyToken() 验证我们的token信息并解析token中的内容
2.生成不携带自定义信息 JWT token
第一步:构建头部信息
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
第二步:构建密钥信息
Algorithm algorithm = Algorithm.HMAC256("secret");
第三步:我们通过定义注册和自定义声明 并组合头部信息和密钥信息生成jwt token
String token = JWT.create()
.withHeader(map)// 设置头部信息 Header
.withIssuer("SERVICE")//设置 载荷 签名是有谁生成 例如 服务器
.withSubject("this is test token")//设置 载荷 签名的主题
// .withNotBefore(new Date())//设置 载荷 定义在什么时间之前,该jwt都是不可用的.
.withAudience("APP")//设置 载荷 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) //设置 载荷 生成签名的时间
.withExpiresAt(expireDate)//设置 载荷 签名过期的时间
.sign(algorithm);//签名 Signature
详细代码如下:
@Test
public void createToken() {
String secret = "secret";// token 密钥
Algorithm algorithm = Algorithm.HMAC256("secret");
// 头部信息
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
Date nowDate = new Date();
Date expireDate = getAfterDate(nowDate, 0, 0, 0, 2, 0, 0);// 2小过期
String token = JWT.create()
.withHeader(map)// 设置头部信息 Header
.withIssuer("SERVICE")//设置 载荷 签名是有谁生成 例如 服务器
.withSubject("this is test token")//设置 载荷 签名的主题
// .withNotBefore(new Date())//设置 载荷 定义在什么时间之前,该jwt都是不可用的.
.withAudience("APP")//设置 载荷 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) //设置 载荷 生成签名的时间
.withExpiresAt(expireDate)//设置 载荷 签名过期的时间
.sign(algorithm);//签名 Signature
Assert.assertTrue(token.length() > 0);
}
3.生成携带自定义信息 JWT token
自定义信息通过 withClaim 方法进行添加,具体操作如下:
JWT.create()
.withHeader(map)
.withClaim("loginName", "zhuoqianmingyue")
.withClaim("userName", "张三")
.withClaim("deptName", "技术部")
生成携带自定义信息 JWT token 详细代码如下:
@Test
public String createTokenWithChineseClaim() {
Date nowDate = new Date();
Date expireDate = getAfterDate(nowDate, 0, 0, 0, 2, 0, 0);// 2小过期
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create().withHeader(map)
/* 设置 载荷 Payload */
.withClaim("loginName", "zhuoqianmingyue").withClaim("userName", "张三").withClaim("deptName", "技术部")
.withIssuer("SERVICE")// 签名是有谁生成 例如 服务器
.withSubject("this is test token")// 签名的主题
// .withNotBefore(new Date())//定义在什么时间之前,该jwt都是不可用的
.withAudience("APP")// 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) // 生成签名的时间
.withExpiresAt(expireDate)// 签名过期的时间
/* 签名 Signature */
.sign(algorithm);
Assert.assertTrue(token.length() > 0);
return token;
}
4.验证 JWT Token
第一步:构建密钥信息
Algorithm algorithm = Algorithm.HMAC256("secret");
第二步:通过密钥信息和签名的发布者的信息生成JWTVerifier (JWT验证类)
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("SERVICE")
.build();
不添加 .withIssuer(“SERVICE”) 也是可以获取 JWTVerifier 。
第三步:通过JWTVerifier 的verify获取 token中的信息。
DecodedJWT jwt = verifier.verify(token);
如下面代码所示就可以获取到我们之前生成 token 的 签名的主题,观众 和自定义的声明信息。
String subject = jwt.getSubject();
List<String> audience = jwt.getAudience();
Map<String, Claim> claims = jwt.getClaims();
for (Entry<String, Claim> entry : claims.entrySet()) {
String key = entry.getKey();
Claim claim = entry.getValue();
System.out.println("key:"+key+" value:"+claim.asString());
}
验证 JWT Token 详细代码如下:
@Test
public void verifyToken() throws UnsupportedEncodingException {
String token = createTokenWithChineseClaim2();
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm).withIssuer("SERVICE").build(); // Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
String subject = jwt.getSubject();
List<String> audience = jwt.getAudience();
Map<String, Claim> claims = jwt.getClaims();
for (Entry<String, Claim> entry : claims.entrySet()) {
String key = entry.getKey();
Claim claim = entry.getValue();
log.info("key:" + key + " value:" + claim.asString());
}
Claim claim = claims.get("loginName");
log.info(claim.asString());
log.info(subject);
log.info(audience.get(0));
}
public String createTokenWithChineseClaim2() throws UnsupportedEncodingException {
Date nowDate = new Date();
Date expireDate = getAfterDate(nowDate, 0, 0, 0, 2, 0, 0);// 2小过期
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
User user = new User();
user.setUserNaem("张三");
user.setDeptName("技术部");
Gson gson = new Gson();
String userJson = gson.toJson(user);
String userJsonBase64 = BaseEncoding.base64().encode(userJson.getBytes());
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create().withHeader(map)
.withClaim("loginName", "zhuoqianmingyue").withClaim("user", userJsonBase64).withIssuer("SERVICE")// 签名是有谁生成
.withSubject("this is test token")// 签名的主题
// .withNotBefore(new Date())//该jwt都是不可用的时间
.withAudience("APP")// 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) // 生成签名的时间
.withExpiresAt(expireDate)// 签名过期的时间
.sign(algorithm);//签名 Signature
return token;
}
三.使用JWT登录案例实战
参考 https://gitee.com/tang_sheng_sheng/jwt-demo.git