无状态认证规范JWT以及JWT.io

JWT简介

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(搭配HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。多用于无状态的身份认证(无状态指服务端不会持久化token).

JWT构造

  • token表现形式

    xxxxx.yyyyy.zzzzz

    分别为Header(头),Payload(有效载荷),Signature(签名)

构造详解

  • 第一部分为Header,包含了两个字段,一个表示为采用JWT加密的type字段,二个为token中第三个参数加密的算法(HMAC SHA256,RSA).最终将两个字段构造为json格式经过BASE64Url加密成为密钥的第一部分。
1
2
3
4
{
"typ": "JWT",
"alg": "HS256"
}
  • 第二部分为Payload,包含了实体(通常为用户信息),以及其他数据的声明.最终也会作为json格式通过BASE64Url方式加密形成token第二部分
    Payload包含了三种声明:
  1. 已注册的声明(官方已经预定义),包括iss(发行人),exp(到期时间),sub(主题),aud(观众)等.声明的名称不超过三个字符.
  2. 公开声明,由使用JWT的人随意定义,公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
  3. 私有声明,由提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
  4. 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    { 
    "iss": "Online JWT Builder",
    "iat": 1416797419,
    "exp": 1448333419,
    "aud": "www.gusibi.com",
    "sub": "uid",
    "nickname": "goodspeed",
    "username": "goodspeed",
    "scopes": [ "admin", "user" ]
    }
  5. 部分已注册声明:

    1
    2
    3
    4
    5
    6
    7
    iss: 该JWT的签发者,是否使用是可选的;
    sub: 该JWT所面向的用户,是否使用是可选的;
    aud: 接收该JWT的一方,是否使用是可选的;
    exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
    iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
    nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;
    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  • 第三部分为Signature,创建Signature(签名)部分,必须采用编码Header(标头)编码的Payload(有效负载),secret(秘密,可以是随机生成的盐值或其他加密使用的字符串),Header(标头)中指定的算法,并对其进行签名.如用HMACSHA256加密:
    1
    2
    3
    4
    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)

通过java程序验证token加密方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.tanmibo.study.mystudynotes.JWTtest;


import org.apache.tomcat.util.codec.binary.Base64;
import org.bouncycastle.crypto.RuntimeCryptoException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class JWTtest {
private static String encrytSHA256(String content, String secret) {

try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] keyBytes = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));
String sign = new String(Base64.encodeBase64(hmacSha256.doFinal(content.getBytes("UTF-8")),true));
return sign;
} catch (Exception e) {
throw new RuntimeCryptoException("加密异常");
}

}

public static void main(String[] args) {
String header = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
System.out.println("header BASE64URL加密后:"+Base64.encodeBase64String(header.getBytes()));
String payload = "{\"exp\":1533192702,\"username\":\"tanmb\"}";
System.out.println("payload BASE64URL加密后:"+Base64.encodeBase64String(payload.getBytes()));
String secret = "dvjM9xT3zWUzqtr4";
String message = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
"eyJleHAiOjE1MzMxOTI3MDIsInVzZXJuYW1lIjoidGFubWIifQ";
//(HMAC计算返回原始二进制数据后进行Base64编码)
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
"eyJleHAiOjE1MzMxOTI3MDIsInVzZXJuYW1lIjoidGFubWIifQ." +
"wwS4uSjq0_HBd-7QFO0EzyEC7IF_4DQszxxYZFpZG4k";
System.out.println("sign为前两者加盐值HMAC计算返回原始二进制数据后进行Base64编码:");
System.out.println(encrytSHA256(message,secret));
System.out.println("token为:");
System.out.println(token);
}
}
  • 控制台输出结果为
    1
    2
    3
    4
    5
    6
    7
    header BASE64URL加密后:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    payload BASE64URL加密后:eyJleHAiOjE1MzMxOTI3MDIsInVzZXJuYW1lIjoidGFubWIifQ==
    sign为前两者加盐值HMAC计算返回原始二进制数据后进行Base64编码:
    wwS4uSjq0/HBd+7QFO0EzyEC7IF/4DQszxxYZFpZG4k=

    token为:
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzMxOTI3MDIsInVzZXJuYW1lIjoidGFubWIifQ.wwS4uSjq0_HBd-7QFO0EzyEC7IF_4DQszxxYZFpZG4k

!! 其中JWT包中对/ =等字符做过处理

java中的JWT常用的方法.

  • 创建token
  1. HS256方式
1
2
3
4
5
6
7
8
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}
  1. RS256方式
1
2
3
4
5
6
7
8
9
10
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}
  • 验证token
  1. HS256方式
1
2
3
4
5
6
7
8
9
10
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
  1. RS256方式
1
2
3
4
5
6
7
8
9
10
11
12
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
  • 解码token,并取其中的声明
1
2
3
4
5
6
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//Invalid token
}
  • 解码后,通过方法获取各种参数
  1. Header
1
2
3
4
String algorithm = jwt.getAlgorithm(); //获取加密算法
String type = jwt.getType(); //获取类型
String contentType = jwt.getContentType();
String keyId = jwt.getKeyId();

私人声明

1
Claim claim = jwt.getHeaderClaim("owner");
  1. Payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Issuer ("iss")->发行人
String issuer = jwt.getIssuer();
Subject ("sub")->主题
String subject = jwt.getSubject();
Audience ("aud")->观众
List<String> audience = jwt.getAudience();
Expiration Time ("exp")->到期时间
Date expiresAt = jwt.getExpiresAt();
Not Before ("nbf")->在....之前不
Date notBefore = jwt.getNotBefore();
Issued At ("iat")->发行于....
Date issuedAt = jwt.getIssuedAt();
JWT ID ("jti")
String id = jwt.getId();

私人声明

1
2
3
4
5
Map<String, Claim> claims = jwt.getClaims();
//Key is the Claim name
Claim claim = claims.get("isAdmin");
//或者
Claim claim = jwt.getClaim("isAdmin");

附:
java-jwt包传送门
我的GitHub同性交友首页tanmibo.github.io