共计 7299 个字符,预计需要花费 19 分钟才能阅读完成。
目录
一、JWT
1、什么是 Jwt
2、为什么要使用 Jwt
3、应用场景
4.Jwt 的组成
4.2、Payload
4.3、signature
二、Jwt 验证过程
1、生成 Jwt 令牌
2、解析旧的 Jwt
3、复制 Jwt
4、Jwt 有效时间测试
三、Jwt 令牌刷新思路
1、配置 JwtFilter 过滤器
2、登录生成 Jwt 令牌
3、配置 CorsFilter 过滤器
4、Vuex 存储 Jwt 令牌
5、修改 main.js
6、配置 axios 请求响应头
前言:
随着前后端分离的发展,以及数据中心的建立,越来越多的公司会创建一个中心服务器,服务于各种产品线。而这些产品线上的产品,它们可能有着各种终端设备,包括但不仅限于浏览器、桌面应用、移动端应用、平板应用、甚至智能家居。
这些设备与中心服务器之间会进行 http 通信,一般来说,中心服务器至少承担着认证和授权的功能。例如登录:各种设备发送消息到中心服务器,然后中心服务器响应一个身份令牌,当这种结构出现后,就出现一个问题:它们之间还能使用传统的 cookie 方式传递令牌信息吗?
其实,也是可以的,因为 cookie 在传输中无非是一个消息头而已,只不过浏览器对这个消息头有特殊处理罢了。但浏览器之外的设备肯定不喜欢 cookie,因为浏览器有着对 cookie 完善的管理机制,但是在其他设备上,就需要开发者自己手动处理了。
JWT 的出现就是为了解决这个问题!!!
一、JWT
1、什么是 Jwt
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。是目前最流行的跨域身份验证解决方案;
RFC 7519:RFC 7519 – JSON Web Token (JWT)
2、为什么要使用 Jwt
它要解决的问题,就是为多种终端设备,提供 统一的、安全的 令牌格式。因此,jwt 只是一个令牌格式而已,你可以把它存储到 cookie,也可以存储到 localstorage,没有任何限制!同样的,对于传输,你可以使用任何传输方式来传输 jwt,一般来说,我们会使用消息头 header 来传输它。
比如:当登录成功后,服务器可以给客户端响应一个 Jwt。
HTTP/1.1 200 OK
...
set-cookie:token=jwt 令牌
authorization:jwt 令牌
...
{..., token:jwt 令牌}
可以看到,jwt 令牌可以出现在响应的任何一个地方,客户端和服务器自行约定即可。
当然,它也可以出现在响应的多个地方,比如为了充分利用浏览器的 cookie,同时为了照顾其他设备,也可以让 jwt 出现在 set-cookie 和 authorization 或 body 中,尽管这会增加额外的传输量。
总之,Jwt 的精髓在于 去中心化,数据是保存在客户端的。
3、应用场景
- 认证:认证是 JWT 的最常用场景。只要用户完成登录,其随后的请求都会包含 JWT,以允许用户访问经由当前 JWT 授权的路由、服务或者是资源。由于开销小且能够被简单应用在跨域访问上,JWT 在分布式站点上所支持的单点登录(SSO)已经是当前它被广泛应用的一个特性。
- 信息交换 :JWT 是一种在各参与方之间安全传递信息的良好方法。由于 JWT 可以被签名(例:使用公钥 / 秘钥对),因而可用于确认发送者自称的身份。除此之外,由于signature 使用 header 和payload进行计算,也可以验证内容没有被篡改。
JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密。
4.Jwt 的组成
一个 Jwt 实际上就是一个字符串,它由三部分组成:
-
Header(头部)
-
Payload(载荷)
-
Signature(签名)
eyJhbGciOiJIUzI1NiJ9.
eyJzdWIiOiJ7fSIsImlzcyI6InpraW5nIiwiZXhwIjoxNTYyODUwMjM3LCJpYXQiOjE1NjI4NDg0MzcsImp0aSI6ImM5OWEyMzRmMDc4NzQyZWE4YjlmYThlYmYzY2VhNjBlIiwidXNlcm5hbWUiOiJ6c3MifQ.
WUfqhFTeGzUZCpCfz5eeEpBXBZ8-lYg1htp-t7wD3I4
它是一个很长的字符串因此,中间用点(.)分隔成三个部分。
注意,Jwt 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
典型的 JWT header 包含两个部分:
-
typ(type)属性,用来标识整个 token 字符串是一个 JWT 字符串,即 JWT。
-
alg(algorithm)属性,用来说明这个 JWT 签发的时候所使用的签名和摘要算法,比如 HMAC SHA256 或者RSA。
{
"alg": "HS256",
"typ": "JWT"
}
4.2、Payload
JWT token 的第二部分是包含了声明的 payload,声明(要求)是一个实体的表述加上额外信息,一共有三种形式的声明:注册、公有和私有;
它的 json 结构实际上是对 JWT 要传递的数据的一组声明,这些声明被 JWT 标准称为 claims,它的一个属性值对其实就是一个 claim(要求),每一个 claim 的都代表特定的含义和作用。
{
"iss":"发行者",
"iat":"发布时间",
"exp":"到期时间",
"sub":"主题",
"aud":"听众",
"nbf":"在此之前不可用",
"jti":"JWT ID"
}
属性 | 说明 |
---|---|
iss(Issuser) | 代表这个 JWT 的签发主体 |
sub(Subject) | 代表这个 JWT 的主体,即它的所有人 |
aud(Audience) | 代表这个 JWT 的接收对象 |
exp(Expiration time) | 是一个时间戳,代表这个 JWT 的过期时间 |
nbf(Not Before) | 是一个时间戳,代表这个 JWT 生效的开始时间,意味着在这个时间之前验证 JWT 是会失败的 |
iat(Issued at) | 是一个时间戳,代表这个 JWT 的签发时间 |
jti(JWT ID) | 是 JWT 的唯一标识 |
公有声明可以加入任何信息,一般会添加用户相关信息或者业务需要的信息,但不建议添加敏感信息,因为该部分会在客户端解密。
私有声明是提供者和使用者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密,基本等同于明文信息。
如果把 JWT 用于认证,那么 JWT 标准内规定的几个 claim 就足够用了,甚至只需要其中一两个就可以了,假如想往 JWT 里多存一些用户业务信息,比如角色和用户名等,这倒是可以用私有声明来添加。
4.3、signature
JWT 的第三部分是签名信息,Signature 由三部分组成:
签名需要将 base64 加密后的 header 和payload使用 .
连接,然后通过 header 所使用的加密方式进行加盐(secret)组合加密,产生了 jwt 的第三部分。以使用 HMAC SHA256 算法为例;
按照前面 alg 可用值的说明,HS256 其实包含的是两种算法:HMAC 算法和 SHA256 算法,前者用于生成摘要,后者用于对摘要进行数字签名。这两个算法也可以用 HMAC SHA256 来统称;
HMACSHA256(base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的 token,它还可以验证 JWT 的发送方是否为它所称的发送方。
算法不同,签名结果不同。
二、Jwt 验证过程
可将绑定的资源中 src(ElementUI 之 Jwt) 目录下的 JwtUtils.java
和JwtDemo.java
导入到项目中的 util 包下:
1、生成 Jwt 令牌
// 定义私有声明
Map claims = new HashMap();
claims.put("username", "zss");
claims.put("age", 18);
// 根据私有声明和 JWT 的 token 有效时间生成 JWT
String jwt = JwtUtils.createJwt(claims, JwtUtils.JWT_WEB_TTL);
// 获取 JWT 中的声明信息,包括:私有声明和标准声明
Claims parseJwt = JwtUtils.parseJwt(jwt);
for (Map.Entry entry : parseJwt.entrySet()) {System.out.println(entry.getKey() + "=" + entry.getValue());
}
// 获取 token 令牌的签发时间
Date d1 = parseJwt.getIssuedAt();
// 获取 token 令牌的过期时间
Date d2 = parseJwt.getExpiration();
System.out.println("令牌签发时间:" + sdf.format(d1));
System.out.println("令牌过期时间:" + sdf.format(d2));
2、解析旧的 Jwt
解析旧的 JWT。如果超过 JWT 本身定义的过期时间,则直接抛出ExpiredJwtException
// 旧的 JWT 令牌
String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjM2NzY0NDQsImlhdCI6MTU2MzY3NDY0NCwiYWdlIjoxOCwianRpIjoiMzE5MmYxOTg4NzFkNGVkZWIyMzU0MmY3NWVhMWI5NDciLCJ1c2VybmFtZSI6InpzcyJ92.4VnkdvGNNe8U1EiKaLz7h6bOJkVGSGtfqojcP_y-0Cc";
Claims parseJwt = JwtUtils.parseJwt(oldJwt);
for (Map.Entry entry : parseJwt.entrySet()) {System.out.println(entry.getKey() + "=" + entry.getValue());
}
Date d1 = parseJwt.getIssuedAt();
Date d2 = parseJwt.getExpiration();
System.out.println("令牌签发时间:" + sdf.format(d1));
System.out.println("令牌过期时间:" + sdf.format(d2));
3、复制 Jwt
模拟 Session 功能(Tomcat 默认 session 过期时间:30 分钟)。当有访问的情况下,自动延长 jwt 中的 token 令牌过期时间,在原有时间上 +30 秒;
String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjM2NzUzMDAsImlhdCI6MTU2MzY3MzUwMCwiYWdlIjoxOCwianRpIjoiYjY5MmE5ZjZkMGZhNDMyN2I1YWY2NTI5OGMzMjQ5MTYiLCJ1c2VybmFtZSI6InpzcyJ9.asHGMPFKURMLnooK29abAuKEdLOHBycAuTovNuGQio0";
String jwt = JwtUtils.copyJwt(oldJwt, JwtUtils.JWT_WEB_TTL);
Claims parseJwt = JwtUtils.parseJwt(jwt);
for (Map.Entry entry : parseJwt.entrySet()) {System.out.println(entry.getKey() + "=" + entry.getValue());
}
Date d1 = parseJwt.getIssuedAt();
Date d2 = parseJwt.getExpiration();
System.out.println("令牌签发时间:" + sdf.format(d1));
System.out.println("令牌过期时间:" + sdf.format(d2));
4、Jwt 有效时间测试
生成新的 Jwt 令牌,设置令牌有效时间 3 秒钟,在 3 秒钟后再次解析 Jwt 令牌提示令牌已过期。
Map claims = new HashMap();
claims.put("username", "zss");
String jwt = JwtUtils.createJwt(claims, 3 * 1000L);
System.out.println(jwt);
Claims parseJwt = JwtUtils.parseJwt(jwt);
Date d1 = parseJwt.getIssuedAt();
Date d2 = parseJwt.getExpiration();
System.out.println("令牌签发时间:" + sdf.format(d1));
System.out.println("令牌过期时间:" + sdf.format(d2));
三、Jwt 令牌刷新思路
使用 ssm+vue+elementui 实现 jwt 验证。
1、配置 JwtFilter 过滤器
导入 JwtFilter.java
到 utils 包下,并注意配置顺序问题:CorsFilter
> JwtFilter
> SpringMVC 核心控制器
jwtFilter
com.wsl.ssm.jwt.JwtFilter
jwtFilter
/*
2、登录生成 Jwt 令牌
在登录成功后,使用 JwtUtil 工具类生成 JWT,并将 Jwt 令牌添加到响应头(response)中带到前端页面。在这里可以将用户对应用户基本信息和用户角色信息保存到私有 claim 中。
if(userVo.getUsername().equals("admin")&&userVo.getPassword().equals("123")){
// 私有要求 claim
Map json=new HashMap();
json.put("username", userVo.getUsername());
// 生成 JWT,并设置到 response 响应头中
String jwt=JwtUtils.createJwt(json, JwtUtils.JWT_WEB_TTL);
response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
return new JsonResponseBody("用户登陆成功!",true,0,null);
}else{return new JsonResponseBody("用户名或密码错误!",false,0,null);
}
3、配置 CorsFilter 过滤器
浏览器只能访问默认的响应头,例如:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma 等 , 如果想让浏览器能访问到其他的响应头的话,需要在服务器上设置。在 CorsFilter 跨域过滤器的 doFilter 方法中配置其他响应头的支持。
// 允许客户端发一个新的请求头 jwt
httpResponse.setHeader("Access-Control-Allow-Headers","Origin,X-Requested-With, Content-Type, Accept, jwt");
// 允许客户端处理一个新的响应头 jwt
httpResponse.setHeader("Access-Control-Expose-Headers", "jwt");
4、Vuex 存储 Jwt 令牌
使用 vuex
保存后端服务器提交过来的 Jwt
,分别在state、getters、mutation
中定义信息。
- state.js
export default {jwt:null}
- getters.js
export default {getJwt:(state)=>{return state.jwt;} }
- mutation.js
export default {setJwt:function (state, payload){state.jwt = payload.jwt;} }
5、修改 main.js
解决 axios 从响应头获得 jwt 令牌并保存到 vuex,通过修改 main.js 中的配置 window.vm=new Vue({}); 获取 Vue 根实例对象。
window.vm = new Vue({
el: '#app',
router,
store,
components: {App},
template: ' '
})
6、配置 axios 请求响应头
在 vue 的 http.js 中配置 axios 的请求响应拦截器,用于存取 Jwt,并将 Jwt 信息保存到 Vuex 中。
// 请求拦截器
axios.interceptors.request.use(function(config) {
let jwt=window.vm.$store.getters.jwt;
//console.log(jwt);
if(jwt)
config.headers['jwt']=jwt;
return config;
}, function(error) {return Promise.reject(error);
});
// 响应拦截器
axios.interceptors.response.use(function(response) {let jwt=response.headers['jwt'];
if(jwt)
window.vm.$store.commit('setJwt',{jwt:jwt});
return response;
}, function(error) {return Promise.reject(error);
});
注:可通过使用 vuex-along 解决刷新 state 初始化问题。
原文地址: JSON Web Token (JWT)的简单介绍、验证过程及令牌刷新思路