1、JWT简介
JWT:Json Web Token,是基于Json的一个公开规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。、
是一种开发的行业标准RFC 7519,用于安全的表示双方之间的声明。目前,jwt广泛的用在系统的用户认证方面,特别是前后端分离项目。
他的两大使用场景是:认证和数据交换
使用起来就是,由服务端根据规范生成一个令牌(token),并且发放给客户端。此时客户端请求服务端的时候就可以携带者令牌,以令牌来证明自己的身份信息。
作用:类似session保持登录状态 的办法,通过token来代表用户身份。
2、jwt认证流程
在项目开发中,一般回按照下图所示的过程进行认证,即:用户登陆成功之后,服务端给用户浏览器返回一个token,以后用户浏览器要携带token再去向服务端发送请求,服务端校验token的合法性,合法则给用户看数据,否则,返回一些错误信息。
传统token方式和jwt在认证方面有什么差异?
传统token方式
用户登陆成功后,服务器生成一个随机token给用户,并且在服务端(数据库或缓存)中保留一份token,以后用户再来访问时需要携带token,服务端接收到token】之后,去数据库或缓存中进行校验token是否超时,是否合法。
jwt方式:
用户登陆成功后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户在来访问时需要携带token,服务端接受到token之后,通过jwt对token进行校验是否超时,是否合法。
3、jwt创建token(数据结构)
jwt生成的token格式如下,即:由 .
连接的三段字符串组成。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT 的三个部分依次如下。
- Header(头部)
- Payload(负载)
- Signature(签名)
写成一行,就是下面的样子。
Header.Payload.Signature
3.1 Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{
"alg": "HS256",
"typ": "JWT"
}
上面代码中,alg
属性表示签名的算法(algorithm),默认是 HMACSHA256(写成 HS256);
typ
属性表示这个令牌(token)的类型(type)。
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。
3.2、Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
3.3、Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMACSHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.
)分隔,就可以返回给用户。
3.4、 Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+
、/
和=
,在 URL 里面有特殊含义,
所以要被替换掉:=
被省略、+
替换成-
,/
替换成_
。这就是 Base64URL 算法。
4、jwt校验token
一般在认证成功后,把jwt生成的token返回给用户,以后用户再次访问时需要携带token,此时要对token进行超时
及合法性
校验。
获取token之后,回按照一下步骤进行校验:
- 将token分割成
header
、payload
、 signature 三部分
jwt_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
JwtUtils : 生成、校验jwt的工具类
package com.example.demo.utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
// jwt生成、校验工具类
public class JwtUtils {
/* 默认head */
public static final String DEFAULT_HEADER = "{\"alg\": \"HS256\",\"typ\": \"JWT\"}";
/* HmacSHA256 加密算法 秘钥 */
public static final String SECRET = "12345";
/* token有效时间 1天 */
public static final long EXPIRE_TIME = 1000*60*60*24;
/* token在header中的名字 */
public static final String HEADER_TOKEN_NAME = "Authorization";
/** Base64URL 编码 */
public static String encode(String input) {
return Base64.getUrlEncoder().encodeToString(input.getBytes());
}
/** Base64URL 解码 */
public static String decode(String input) {
return new String(Base64.getUrlDecoder().decode(input));
}
/**
* signature 签名(即:对 header、payload进行加密)
* HmacSHA256 加密算法
* @param data 要加密的数据
* @param secret 秘钥
*/
public static String HMACSHA256(String data, String secret) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
}
return sb.toString().toUpperCase();
}
/** 获取签名 */
public static String getSignature(String payload) throws Exception {
return HMACSHA256(encode(DEFAULT_HEADER)+"."+encode(payload),SECRET);
}
/**
* 验证jwt,正确返回载体数据,错误返回null
* @param jwt
*/
public static String checkJwt(String jwt) throws Exception {
String[] jwts = jwt.split("\\.");
/* 验证签名 */
if (!HMACSHA256(jwts[0]+"."+jwts[1],SECRET).equals(jwts[2])){
return null;
}
/* 验证头部信息 */
if (!jwts[0].equals(encode(DEFAULT_HEADER))){
return null;
}
return decode(jwts[1]);
}
}
-
jwt_token
- 对第一部分
header_segment
进行base64url解密,得到header - 对第二部分payload_segment进行base64url解密,得到payload
- 对第三部分crypto_segment进行base64url解密,得到
signature
- 对第三部分
signature
部分数据进行合法性校验
- 拼接前两段密文,即:
signing_input
- 从第一段明文中获取加密算法,默认:
HS256
- 使用 算法+盐 对
sign_input
进行加密,将得到的结果和signature
密文进行比较
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
5、token 的续签问题
token 有效期一般都建议设置的不太长,那么 token 过期后如何认证,如何实现动态刷新 token,避免用户经常需要重新登录?
我们先来看看在 Session 认证中一般的做法:假如 session 的有效期30分钟,如果 30 分钟内用户有访问,就把 session 有效期被延长30分钟。
- 类似于 Session 认证中的做法:这种方案满足于大部分场景。假设服务端给的 token 有效期设置为30分钟,服务端每次进行校验时,如果发现 token 的有效期马上快过期了,服务端就重新生成 token 给客户端。客户端每次请求都检查新旧token,如果不一致,则更新本地的token。这种做法的问题是仅仅在快过期的时候请求才会更新 token ,对客户端不是很友好。
- 每次请求都返回新 token :这种方案的的思路很简单,但是,很明显,开销会比较大。
- token 有效期设置到半夜 :这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
- 用户登录返回两个 token :第一个是 acessToken ,它的过期时间 token 本身的过期时间比如半个小时,另外一个是 refreshToken 它的过期时间更长一点比如为1天。客户端登录后,将 accessToken和refreshToken 保存在本地,每次访问将 accessToken 传给服务端。服务端校验 accessToken 的有效性,如果过期的话,就将 refreshToken 传给服务端。如果有效,服务端就生成新的 accessToken 给客户端。否则,客户端就重新登录即可。该方案的不足是:1⃣️需要客户端来配合;2⃣️用户注销的时候需要同时保证两个 token 都无效;3⃣️重新请求获取 token 的过程中会有短暂 token 不可用的情况(可以通过在客户端设置定时器,当accessToken 快过期的时候,提前去通过 refreshToken 获取新的accessToken)。
6、JWT优点:
- jwt基于json,非常方便解析;
- 可以再令牌中自定义丰富的内容,易扩展(payload可以扩展);
- 通过签名,让JWT防止被篡改,安全性高;
- 资源服务使用JWT可不依赖认证服务即可完成授权。
7、JWT 的几个特点
(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。
(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
8、总结:
深入了解jwt方案的优缺点