1. 什么是 JWT
JWT 其全称为: JSON web Token, 简单地说就是 JSON 在 Web 上的一种带签名的标记形式. 官方的定义如下:
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
即: JSON Web Token (JWT)是一个开放标准(RFC 7519), 它定义了一种紧凑的, 自包含的方式, 用于作为 JSON 对象在各方之间安全地传输信息.
2. 有什么作用
对信息进行签名之后再进行传输有什么作用, JWT 就有什么作用. 它能起的作用, 决定了在项目的需求中是否有必要使用它, 它自身的本质决定了它适合的场景.
本质上, JWT 跟自己对信息加个签名没有区别.
那使用它的理由是什么呢?
(1)它建立了一个标准并为多数人认识和接受, 这样一来就可以形成标准库, 使用者可以共享.
(2)它形成了一些最佳实践, 这种实践过程包括了参数安全传递的诸多常见方面, 如 exp 到期时间属性的定义来规定签名有效期等. 按照最佳实践中对一些 JSON 属性的明确定义, 再加上标准库对它的贯彻实现, 会带来很多便利.
(3)将其作为 Token 放在请求的 header 中, 作为无状态的鉴权方式很适合目前多站点应用的场景.
但最佳实践和其特性不能混为一谈, 具体到应用场景, 仍然可以利用其特性作适合该场景的其它发挥.
3. 参数访问控制演化
(1)直接传参
http://*/API?p1=*&p2= http://*/API?p1=*&p2= *
这种方式, 不进行访问的权限的判断, 公开可直接访问.
(2)带 KEY 传参
http://*/API?p1=*&p2=*&key= http://*/API?p1=*&p2=*&key=
这种方式需要知道正确的 KEY 才能访问, 但 KEY 明文附在后面易泄露.
(3)带签名传参
这种方式, 将 KEY 作为签名算法的加密条件, 不明文显示, 不知道 KEY 则无法生成相应的签名, 感觉不错. 不足在于, 签名一次之后访问链接一直为有效会带有风险.
http://*/API?p1=*&p2=*&sign= http://*/API?p1=*&p2=*&sign=
其中签名部分, 如采用 md5 方式, key 作为运算的一部分.
sign=md5(p1+p2+key)
(4)带时间戳签名
参数中带上签名时的时间戳, 时间戳会参与签名算法, 服务端不仅检测签名的有效性, 还会比较时间是否在合理范围内, 如 5 分钟以内, 如此一来, 链接在一段时间之后就会失效.
http://*/API?p1=*&p2=*×tamp=*&sign= http://*/API?p1=*&p2=**tamp=*&sign=
其中签名部分, 如采用 md5 方式, time,key 均作为运算的一部分.
sign=md5(p1+p2+time+key)
(5)独立鉴权参数签名
将鉴权部分独立出来签名, 这样的好处就是鉴权部分独立的判断过程, 其它形参不再需要参与这个签名与判断过程.
参数可使用 JSON 形式, 于是可以让其变成以下形式:
鉴权传输部分形式如:{p1:abcd,p2:abcd}.sign
其中, 签名部分, 如采用 md5 方式, 将 JSON 字符串与 key 拼接运算, 并且使用连接符. 点, 如下.
sign=md5({p1:abcd,p2:abcd}.key)
(6)带头部的独立鉴权部分
为了更加灵活的, 将鉴权部分加个头部. 头部用来干什么呢, 可以指定签名算法, 或以后可能要更多扩展参数用, 如以下形式.
{alg:MD5}.{p1:abcd,p2:abcd}.sign
签名部分, 为前两部分再连接上 key 一起运算.
sign=md5({alg:MD5}.{p1:,p2:}.key)
(7)最终标准化为 JWT 形式
头部称之为 header, 数据部分称之为 payload, 签名部分为 signature.
(7.1) header 不使用明文, 采用其 base64 形式
(7.2) payload 不使用明文, 采用其 base64 形式
(7.3) signature 为前两者 (都是 base64 形式) 通过 . 点连接, 再采用 header 中指定的签名算法签名的结果.
(7.4) 最终形式为 base64(header).base64(payload).signature
(7.5) base64 考虑到 URL 编码, 将 = 去掉,+ 号变成 -,/ 变成_ 处理.
(7.6) 最终字符串通过作为请求 header 进行传输.
4. 最简实现
给定一个签名用的 sercretKey 和 payload, 生成成符合要求的 JWT 字符串. 多数时候, 需求可能就是这样简单, 至于签名算法, 这里就使用一般默认的 HS256. 则需要的功能函数大致是:
- func getJwt (payload){
- var content = base64({"alg":"HS256","typ":"JWT"}) + . + base64(payload)
- var signature = base46( sign(content, sercretKey) )
- return content + . + sign
- }
- C# 的实例代码, 这里给出一个 C# 的 JWT 辅助类, 其中 JObject 引用了 Newtonsoft.JSON 包.
- public class JWTHelper
- {
- #region 工具函数准备
- /// <summary>
- /// 对字符串 Base64 编码, 并且替换 = + / 为 "" - _
- /// </summary>
- /// <param name="str"></param>
- /// <returns></returns>
- public static string Base64URL(string str)
- {
- return Convert.ToBase64String(Encoding.UTF8.GetBytes(str)).Replace("=", "").Replace("+","-").Replace("/","_");
- }
- /// <summary>
- /// 对字节数组 Base64 编码, 并且替换 = + / 为 "" - _
- /// </summary>
- /// <param name="bs"></param>
- /// <returns></returns>
- public static string Base64URL(byte[] bs)
- {
- return Convert.ToBase64String(bs).Replace("=", "").Replace("+","-").Replace("/","_");
- }
- /// <summary>
- /// HMAC SHA256
- /// </summary>
- /// <param name="str"></param>
- /// <returns></returns>
- public static string HS256(string str, string key)
- {
- var encoding = new System.Text.UTF8Encoding();
- byte[] keyByte = encoding.GetBytes(key);
- byte[] messageBytes = encoding.GetBytes(str);
- using (var hmacsha256 = new HMACSHA256(keyByte))
- {
- byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
- return Base64URL(hashmessage);
- }
- }
- #endregion
- /// <summary>
- /// 生成签名后的 JWT 最终字符串.
- /// 为了简化示例, 这里使用签名算法就设定为: HS256
- /// header = {"alg":"HS256","typ":"JWT"}
- /// </summary>
- /// <param name="payload"></param>
- /// <param name="key"></param>
- /// <returns></returns>
- public static string Sign(JObject payload, String key)
- {
- JObject header = new JObject();
- header["alg"] = "HS256";
- header["typ"] = "JWT";
- string h = Base64URL(header.ToString(Formatting.None));
- string p = Base64URL(payload.ToString(Formatting.None));
- string s = HS256(h + "." + p, key);
- return String.Format("{0}.{1}.{2}", h, p, s);
- }
- }
使用以下代码测试一下:
- JObject payload = new JObject();
- payload["username"] = "xxx";
- Console.Write(JWTHelper.Sign(payload, "123fd"));
得到结果
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Inh4eCJ9.1C28A5CqMa70FLtUQh4pwSZWPlZhbQ-ZeYs38K_sqks
在 https://jwt.io/ 上, 可以验证一下, 得到了同样的结果.
5. 具体使用
显然, 它也会存在一些问题, 如通过 base64 解码看到明文, 或者是在有效期内取得整个 token 进行访问等. 所以使用是根据需要来的. 而且, 也可以在 JWT 上进一步加入自定义的新机制来应对更多的场景.
以下这篇文章列出了一些问题与趋势, 可供参考.
更多细节可参考: https://www.cnblogs.com/cjsblog/p/9277677.html
来源: https://www.cnblogs.com/timeddd/p/11122045.html