一、认识JWT(Json Web Token)
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,一般来说如果是使用session或者cookie来实现,但是这两者都有一定的局限性。而我们使用JWT去实现跨域认证的话,就可以将JWT放在请求头里面(也有人会把它放在post请求的数据体里,但是这种方法比较少见)。
1.JWT的数据结构
JWT解析出来之后其实就是一个Json,但是在传送的过程中,它是一串很长的字符串,中间用“.”分割成三个部分
- Header(头部)
- Payload(负载)
- Signature(签名)
写成一行就是
Header.Payload.Signature
举一个我在真正开发时遇到的一个JWT,里面的id_token就是一个JWT,可以看到它就是用两个“.”去分割开的。
2.Header(头部)
Header 部分是一个 JSON 对象,描述 JWT 的元数据。
{ "alg": "HS256", "typ": "JWT" }
上面代码中,alg
属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ
属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
。
最后,将上面的 JSON 对象使用 Base64URL 算法成字符串。说到这个,如果你的签名算法是别的算法的话,去解析JWT的时候会报错
Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance
这时候你要去检查一下你的密钥是否正确,我当时的情况就是公钥搞错了,不过我看网上也有人说什么可以设置一下算法的类型,但是看不懂是怎么操作的,全网就搜到了那个方法,全都是一模一样,复制来复制去的,很没意思。
3.Payload(负载)
Payload是JWT最重要的数据,里面会携带很多信息,我们要实现登录认证一般都在里面拿取到我们想要的数据。JWT 规定了7个官方字段供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,我们还可以在生成JWT的时候定义部分私有字段名,下面是我工作是遇到的JWT,他们就定义了自己的私有字段,将payload解析之后就是下面的图片
4.Signature(签名)
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.
)分隔,就可以返回给用户。
5.JWT的使用
一般是放在请求头里面,注意“Bearer”和“token”之间有一个空格。
Authorization: Bearer <token>
二、解析JWT
解析JWT网上有很多个jar可以拿来使用,我这里使用的jose4j来解析。
/** * 验证Jwt并拿到email值 * * @param jwtTicket Jwt票据 * @param publicKeyStr 公钥 */ public String validateAndGetToken(String jwtTicket, String publicKeyStr) throws Exception { JsonWebSignature jws = new JsonWebSignature(); jws.setCompactSerialization(jwtTicket); JsonWebKey jsonWebKey = JsonWebKey.Factory.newJwk(publicKeyStr); String keyId = jsonWebKey.getKeyId(); jws.setKey(jsonWebKey.getKey()); boolean verifySignature = jws.verifySignature(); if (!verifySignature) { throw new Exception("Invalid signature!"); } String payload = jws.getPayload(); JwtClaims claims = JwtClaims.parse(payload); NumericDate expirationTime = claims.getExpirationTime(); Map<String, Object> claimsMap = claims.getClaimsMap(); if (expirationTime == null) { logWarn("Invalid signature!"); return null; } if (!expirationTime.isAfter(NumericDate.now())) { logWarn("Jwt ticket expired!"); return null; } String username = (String) claimsMap.get("email"); return username; }
这里claimsMap.get("email")就是拿到邮箱地址,因为我做的是邮箱的单点登录,所以这里是email,根据具体情况去获取具体的字段就可以了。
参考链接
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html