上期讨论过OAuth2, 是一种身份认证+资源授权使用模式。通过身份认证后发放授权凭证。用户凭授权凭证调用资源。这个凭证就是一种令牌,基本上是一段没什么意义的加密文,或者理解成密钥也可以。服务方通过这个令牌来获取用户身份信息,也就是说服务端必须维护一个已经获得身份验证的用户信息清单。研究了一下JWT,发现它本身可以携带加密后的一些信息包括用户信息,而这些信息又可以通过同样的加密算法解密恢复。也就是说服务端是可以直接对收到的JWT解密恢复用户信息,这样用起来就方便多了。还记着我们的POS例子里客户端必须构建一个指令,如:http://www.pos.com/logIn?shopid=1001&userid=234 这个Uri里的shopid是明码的,会造成很大安全风险。使用JWT后,我们可以把shopid,单号什么的都放在JWT里就安全多了。
先了解一下JWT:JWT也是一个行业标准:RFC7519,是一个用Json格式传递加密信息的方式。JWT的结构如下:
header.payload.signiture 如:hhhhh.ppppp.ssssss
header:由两部分组成:1、令牌类型,在这里是JWT, 2、签名算法如 HMAC SHA256 or RSA, 下面是个header例子:
{ "alg": "HS256", "typ": "JWT" }
payload:可以用来承载用户自定义信息,如userid, shopid, vchnum ...
{ "shopid": "1101", "userid": "102", "vchnum": 12 }
signiture: 就是把 加密后的header+加密后的payload+secret 用header提供的签名算法签名,如下:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
我的目标是把一些用来辨识用户、权限以及状态信息加密存在JWT内发送给用户,用户在请求中提交他的JWT,服务端再解密并取出内部信息然后确定如何处理用户请求。
JWT本身原理并不复杂,应用场景也不是很多,所以不想花太多精力研究它。刚好,找到一个开源的scala JWT工具库jwt-scala. 下面就利用项目源代码来了解一下JWT的操作,包括:加密、解密、验证、获取payload内部claims值。
JWT encode 方法如下:
/** Encode a JSON Web Token from its different parts. Both the header and the claim will be encoded to Base64 url-safe, then a signature will be eventually generated from it if you did pass a key and an algorithm, and finally, those three parts will be merged as a single string, using dots as separator. * * @return $token * @param header $headerString * @param claim $claimString * @param key $key * @param algorithm $algo */ def encode(header: String, claim: String, key: String, algorithm: JwtAlgorithm): String = { val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim) data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm)) }
所以产生JWT的元素都在参数里了。我们可以直接用payload.claims来构建JWT:
/** An alias to `encode` which will provide an automatically generated header. * * @return $token * @param claim $claimString */ def encode(claim: String): String = encode(JwtHeader().toJson, claim) /** An alias to `encode` which will provide an automatically generated header and setting both key and algorithm * to None. * * @return $token * @param claim the claim of the JSON Web Token */ def encode(claim: JwtClaim): String = encode(claim.toJson) def encode(header: String, claim: String): String = { JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim) + "." }
这样看一个正确的JWT可以没有签名那部分的:hhhhh.ppppp。想想还是要用签名,安全点。用下面这个函数就可以了:
/** An alias to `encode` which will provide an automatically generated header and allowing you to get rid of Option * for the key and the algorithm. * * @return $token * @param claim $claimString * @param key $key * @param algorithm $algo */ def encode(claim: String, key: String, algorithm: JwtAlgorithm): String = encode(JwtHeader(algorithm).toJson, claim, key, algorithm) /** Deserialize an algorithm from its string equivalent. Only real algorithms supported, * if you need to support "none", use "optionFromString". * * @return the actual instance of the algorithm * @param algo the name of the algorithm (e.g.