作者:Mars酱

声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联络本人。

转载:欢迎转载,转载前先请联络我!

什么是JWT

JWT的全称是Json Web Token。是根据RFC 7519开放标准的,它界说了一种紧凑且独立的方法,用于在各方之间以 JSON 目标的方法安全地传输信息。此信息能够用作验证和相互信任,因为它是经过数字签名的。JWT 能够运用密钥(运用 HMAC 算法)或运用 RSAECDSA 的公钥/私钥对进行签名。

哪里能用JWT

以下是JWT两种运用场景:

  • 授权:这是运用 JWT 的最常见的运用场景。用户登录后,每个后续恳求都将包含 JWT,答应用户访问运用该令牌答应的路由、服务和资源。单点登录是当今广泛运用 JWT 的一项功用,因为它的开销很小,而且能够跨不同域轻松运用。
  • 信息交换:JWT是在各方之间安全传输信息的比较快捷的方法。因为 JWT 能够签名(例如,运用公钥/私钥对),因而能够确认发送者是否是在您的授权范围之内。而且,因为签名是运用标头和有用负载计算的,因而还能够验证内容是否未被篡改。

JWT的组成

这是一个JWT的token串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

很杂乱,看不懂是不是?其实这一串是经过加密之后的密文字符串,中心经过.来切割。每个.之前的字符串分别表示JWT的三个组成部分:HeaderPayloadSignature

Header(头信息)

Header的首要作用是用来标识。一般是两部分组成:

  • typ:type 的简写,令牌类型,也便是JWT。
  • alg:Algorithm 的简写,加密签名算法。一般运用HS256,jwt官网提供了12种的加密算法,截图如下:

JWT | 一分钟掌握JWT | 概念及实例

Header的明文示例:

{
  "alg": "HS256",
  "typ": "jwt"
}

经过Base64编码之后的明文,变为:

eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9

也便是第一个.之前的密文串。以下是Header部分常用部分的声明:

key name 阐明
typ 令牌类型 假如存在,则有必要将其设置为已注册的 IANA 媒体类型。
cty 内容类型 假如运用嵌套签名或加密,主张将其设置为 ;不然,请省掉此字段。
alg 音讯身份验证代码算法 发行者能够自在设置算法来验证令牌上的签名。可是,某些受支撑的算法不安全。
kid 密钥标识 指示客户端用于生成令牌签名的密钥的提示。服务器将此值与文件上的密钥匹配,以验证签名是否有用以及令牌是否实在。
x5c x.509 证书链 RFC4945 格式的证书链,对应于用于生成令牌签名的私钥。服务器将运用此信息来验证签名是否有用以及令牌是否实在。
x5u x.509 证书链网址 服务器可在其间检索与用于生成令牌签名的私钥对应的证书链的 URL。服务器将检索并运用此信息来验证签名是否实在。
crit 危急 服务器有必要理解的标头列表,以便承受令牌为有用令牌

Payload(负载信息)

也称为JWT claims,放置需求传输的信息,有三类:

  • 保留claims:首要包括iss发行者、exp过期时刻、sub主题、aud用户等。
  • 公共claims:界说新创的信息,比如用户信息和其他重要信息。
  • 私有claims:用于发布者和消费者都赞同以私有的方法运用的信息。

以下是Payload的官方界说内容:

key name 阐明
iss 发送者 标识颁布 JWT 的发送主体。
sub 主题 标识 JWT 的主题。
aud 接收者 标识 JWT 所针对的接收者。每个在处理 JWT 的主体都有必要运用受众声明中的值来标识自己。假如处理的主体在存在此声明时未将自己标识为声明中的值,则有必要拒绝 JWT。
exp 到期时刻 标识不得承受 JWT 进行处理的过期时刻。该值有必要是日期类型,而且是1970-01-01 00:00:00Z 之后的日期秒。
nbf jwt的开端处理的时刻 标识 JWT 开端承受处理的时刻。该值有必要是日期。
iat jwt宣布的时刻 标识 JWT 的宣布的时刻。该值有必要是日期。
jti jwt id 令牌的区别大小写的唯一标识符,即使在不同的颁布者之间也是如此。

Payload明文示例:

{
  "sub": "12344321",
  "name": "Mars酱", // 私有claims
  "iat": 1516239022
}

经过Base64加密之后的明文,变为:

eyJzdWIiOiIxMjM0NDMyMSIsIm5hbWUiOiJNYXJz6YWxIiwiaWF0IjoxNTE2MjM5MDIyfQ

也便是第一个.和第二个. 之间的密文串内容。

Signatrue(签名信息)

Signature 部分是对Header和Payload两部分的签名,作用是防止 JWT 被篡改。这个部分的生成规则首要是是公式(伪代码)是:

Header中界说的签名算法(
    base64编码(header) + "." + base64编码(payload),
    secret
)

secret是存放在服务端加密运用到的盐。

得到签名之后,把Header的密文、Payload的密文、Signatrue的密文按顺序拼接成为一个字符串,中心经过.来连接并切割,整个串便是JWT了。

JWT实例

概念讲完了,咱们来个实例吧,先来一个jwt的编码:

    public static String encodeJWT(String key) {
        // 1. 界说header部分内容 
        Map headerMap = new HashMap();
        headerMap.put("alg", SignatureAlgorithm.HS256.getValue());
        headerMap.put("typ", "JWT");
        // 2. 界说payload部分内容
        Map payloadMap = new HashMap();
        payloadMap.put("sub", "mars酱让你爽一分钟");
        payloadMap.put("iat", UUID.randomUUID());
        payloadMap.put("exp", System.currentTimeMillis() + 24 * 60 * 60 * 1000);
        payloadMap.put("name", "Mars酱");
        payloadMap.put("role", "酱油王");
        // 3.生成token
        String jwtToken = Jwts.builder()
                .setHeaderParams(headerMap)
                .setClaims(payloadMap)
                .signWith(SignatureAlgorithm.HS256, key)
                .compact(); // 拼接header + payload
//        System.out.println(jwtToken);
        return jwtToken;
    }

咱们在main函数中调用这个,运转得到结果:

JWT | 一分钟掌握JWT | 概念及实例

很长的密文字符串,就不截完整了。其间key是盐,jsonwebtoken的jar包规定,key有必要字节数要大于等于你所用的加密算法的最小字节数,mars酱这儿运用的是HS256,最小的key长度规定的便是256:

JWT | 一分钟掌握JWT | 概念及实例

因而,key值我这儿传入了一个超长的key:

String key = "marsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmars";

假如你的key没这么长,你可能会报这样错误:

JWT | 一分钟掌握JWT | 概念及实例

下面再来一个解密jwt的代码,入参为加密后的jwt和盐key:

    public static void decodeJWT(String jwtToken, String key) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(key)
                    .parseClaimsJws(jwtToken)
                    .getBody();
            Object sub = claims.get("sub");
            Object name = claims.get("name");
            Object role = claims.get("role");
            Object exp = claims.get("exp");
            System.out.println("sub:" + sub + "\nname:" + name.toString() + "\nrole:" + role + "\nexp:" + exp + "\n失效了?" + ((System.currentTimeMillis() - (Long)exp) > 0));
        } catch (ExpiredJwtException e) {
            System.out.println("mars酱提醒您:token已过期");
        }
    }

在main中调用后,得到结果:

JWT | 一分钟掌握JWT | 概念及实例

把生成的jwt字符串放入官网( jwt.io )的解密界面,和程序解密的结果一样,是不是很完美?

JWT | 一分钟掌握JWT | 概念及实例

优缺点

好了,概念说完了,实例也给了,想想jwt有什么优缺点?我总结了一下:

优点:

  1. 可扩展性好 应用程序分布式部署的情况下,假如运用session机制,那就要要做多台机器的数据同享,一般能够存在数据库或者redis里面。而运用jwt不需求同享。
  2. jwt是无状况的 jwt不在服务端存储任何状况。RESTful API的准则之一是无状况,宣布恳求时,总会回来带有参数的呼应,不会发生附加影响。用户的认证状况引进这种附加影响,这破坏了这一准则。另外jwt的载荷中能够存储一些常用信息,用于交换信息,有用地运用 JWT,能够下降服务器查询数据库的次数。

缺点:

  1. 安全性 因为jwt的payload是运用base64编码的,并没有加密,因而jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。
  2. 一次性 无状况是jwt的特色,但也导致了这个问题,jwt是一次性的。想修正里面的内容,就有必要签发一个新的jwt。

jwt开源框架

mars酱这儿运用的jwt是io.jsonwebtoken的,它需求在pom中引进依赖:

	<!-- jwt api界说 -->
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt-api</artifactId>
		<version>0.10.2</version>
	</dependency>
	<!-- jwt api impl完成 -->
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt-impl</artifactId>
		<version>0.10.2</version>
	</dependency>
	<!-- jwt json -->
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt-jackson</artifactId>
		<version>0.10.2</version>
	</dependency>

其余的jwt框架全在jwt官网列举了。基本覆盖了全语言

JWT | 一分钟掌握JWT | 概念及实例

好好享用jwt的校验过程吧