一、什么是JWT
JWT全称JSON Web Token,由三部分组成: header(头)、payload(载体)、signature(签名)。 随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
header
JWT第一部分是header,header主要包含两个部分,alg指加密类型,可选值为HS256、RSA等等,typ=JWT为固定值,表示token的类型。Payload
JWT第二部分是payload,payload是token的详细内容,一般包括iss (发行者), exp (过期时间), sub(用户信息), aud (接收者),以及其他信息,详细介绍请参考官网,也可以包含自定义字段。
{
"iat": 1493090001,
"name": "张三"
}
iss:Issuer,发行者
sub:Subject,主题
aud:Audience,观众
exp:Expiration time,过期时间
nbf:Not before
iat:Issued at,发行时间
jti:JWT ID
-
signature
JWT第二部分是signature,这部分的内容是这样计算得来的:
1、EncodeString = Base64(header).Base64(payload)
2、最终token = HS256(EncodeString,"秘钥")
签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。
二、JSON Web Tokens是如何工作的
无论何时用户想要访问受保护的路由或者资源的时候,用户代理(通常是浏览器)都应该带上JWT,典型的,通常放在Authorization header中,用Bearer schema。
header应该看起来是这样的:
Authorization: Bearer <token>
服务器上的受保护的路由将会检查Authorization header中的JWT是否有效,如果有效,则用户可以访问受保护的资源。如果JWT包含足够多的必需的数据,那么就可以减少对某些操作的数据库查询的需要,尽管可能并不总是如此。
如果token是在授权头(Authorization header)中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用cookie。
- 验证过程
- 签名验证
当接收方接收到一个JWT的时候,首先要对这个JWT的完整性进行验证,这个就是签名认证。它验证的方法其实很简单,只要把header做base64url解码,就能知道JWT用的什么算法做的签名,然后用这个算法,再次用同样的逻辑对header和payload做一次签名,并比较这个签名是否与JWT本身包含的第三个部分的串是否完全相同,只要不同,就可以认为这个JWT是一个被篡改过的串,自然就属于验证失败了。接收方生成签名的时候必须使用跟JWT发送方相同的密钥,意味着要做好密钥的安全传递或共享
- 载体验证
iss(Issuser):如果签发的时候这个claim的值是“a.com”,验证的时候如果这个claim的值不是“a.com”就属于验证失败
sub(Subject):如果签发的时候这个claim的值是“liuyunzhuge”,验证的时候如果这个claim的值不是“liuyunzhuge”就属于验证失败
aud(Audience):如果签发的时候这个claim的值是“['b.com','c.com']”,验证的时候这个claim的值至少要包含b.com,c.com的其中一个才能验证通过
exp(Expiration time):如果验证的时候超过了这个claim指定的时间,就属于验证失败;nbf(Not Before):如果验证的时候小于这个claim指定的时间,就属于验证失败
iat(Issued at):它可以用来做一些maxAge之类的验证,假如验证时间与这个claim指定的时间相差的时间大于通过maxAge指定的一个值,就属于验证失败
jti(JWT ID):如果签发的时候这个claim的值是“1”,验证的时候如果这个claim的值不是“1”就属于验证失败
注意:在验证一个JWT的时候,签名认证是每个实现库都会自动做的,但是payload的认证是由使用者来决定的。因为JWT里面可能不会包含任何一个标准的claim,所以它不会自动去验证这些claim。
以登录认证来说,在签发JWT的时候,完全可以只用sub跟exp两个claim,用sub存储用户的id,用exp存储它本次登录之后的过期时间,然后在验证的时候仅验证exp这个claim,以实现会话的有效期管理。
三、实战DEMO
- 验证流程
1.用户携带用户名和密码请求访问
2.服务器校验用户凭据
3.应用提供一个token给客户端
4.客户端存储token,并且在随后的每一次请求中都带着它
5.服务器校验token并返回数据
- 注意
每一次请求都需要token
Token应该放在请求header中
我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin: *
1、引入相关pom
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
2、编写JWT生成解析工具类
public class JWTUtils {
public static String createJWT(String id,String subject,long ttlMillis,SecretKey key){
//获取签名算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuer("user")
.setIssuedAt(now)
.signWith(key,signatureAlgorithm);
if(ttlMillis >= 0){
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
//设置过期时间
builder.setExpiration(expDate);
}
return builder.compact();
}
public static Claims parseJWT(String jwt,SecretKey keySpec){
return Jwts.parser()
.setSigningKey(keySpec)
.parseClaimsJws(jwt)
.getBody();
}
public static ResultCode validateJWT(String jwt){
ResultCode checkResult = new ResultCode();
Claims claims = null;
try {
claims = parseJWT(jwt);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode("解析失败");
checkResult.setSuccess(false);
}
return checkResult;
}
public static void main(String[] args) {
//生成密匙
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jwt = JWTUtils.createJWT("1","test",10000,key);
System.out.println(jwt);
System.out.println(JWTUtils.parseJWT(jwt,key));
}
}
3、控制器层代码
- 登陆逻辑
@RequestMapping("/login")
public String login(用户信息){
//在这里进行用户身份校验
......
......
//校验身份成功生成token返回
String jwt = JWTUtils.createJWT(用户信息);
return jwt;
}
- 所有需要用户身份验证的接口逻辑
//这里只大致写一些,具体验证失败原因以及返回格式问题可以根据自身细调
public String toIndex(HttpServletRequest request){
//获取token
String authorization = request.getHeader("authorization");
if(authorization == null){
//返回登陆
return "需要登陆";
}else{
//进行校验
try{
Claims claims = JWTUtils.parseJWT(authorization);
return "验证成gong";
}catch (Exception e){
e.printStackTrace();
return "验证失败";
}
}
}
3、客户端代码
- 输入用户信息,申请登陆
$.ajax({
url:'http://localhost:8080/demo/login',
type:'GET',
data:userLoginData,
success:function(res) {
localStorage.setItem("token",res)
},
error:function (res) {
alert(res)
}
})
- 当请求需要身份验证接口时
var token = localStorage.getItem("token");
$.ajax({
url:'http://localhost:8080/demo/toIndex',
type:'GET',
header:{
'Authorization':token
},
beforeSend : function(request) {
request.setRequestHeader("Authorization",token );
},
success:function(res) {
},
})
四、参考连接
https://github.com/jwtk/jjwt#install-jdk-maven
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://www.jianshu.com/p/f25d62305c70
https://www.cnblogs.com/cjsblog/p/9277677.html
https://www.jianshu.com/p/fe67b4bb6f2c