JSON Web令牌(JWT)可以轻松地在服务(应用程序/站点的内部和外部)之间发送只读签名的 “ 声明 ” 。声明是任何一些你希望别人能够读取/验证的数据,但不可改变。
IETF的定义:
“ JSON Web Token (JWT)是一种紧凑的 URL安全 方式,用来表示在不同部分之间进行传输的声明,它可被编码 为 JSON对象 , 使用JJSON Web Signature (JWS)进行数字 签名 .“
要识别/验证应用程序中的人员身份,需要在页面(或API端点)的标头Header或网址中放置基于标准的令牌,以证明该用户已登录并允许其访问所需内容。
例: https://www.yoursite.com/private-content/?token=eyJ0eXAiOiJKV1Qi.eyJrZXkiOi.eUiabuiKv
JWT是一串“url safe”字符,用于编码信息,令牌有三个组件(以句点分隔)(此处显示为多行以便于阅读,但用作单个文本字符串)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 // header |
1. Header
JWT的第一部分是一个简单JavaScript对象的编码字符串表示,它描述了令牌以及所使用的散列算法。
2.Payload
JWT的第二部分构成了令牌的核心。Payload有效载荷长度与您在JWT中存储的数据量成比例。一般的经验法则是:将最小值存储在JWT中。
3. Signature签名
JWT的第三部分也是最后一部分是基于Header(第一部分)和正文(第二部分)生成的签名,用于验证 JWT是否有效。
什么是“声明”?
声明是预定义的键及其值:
- iss:令牌的发行者
- exp:到期时间戳(拒绝已过期的令牌)。注意:如规范中所定义,这必须以秒为单位。
- iat:JWT发布的时间。可用于确定JWT的年龄
- nbf:“not before”是令牌变为活动状态的未来时间。
- jti:JWT的唯一标识符。用于防止JWT被重复使用或重放。
- sub:令牌的主题(很少使用)
- aud:令牌的受众(也很少使用)
案例
一个简单的例子。(完整源代码位于/ example目录中):
https://jwt.herokuapp.com/
该服务器架构使用node.js http服务器,我们在/example/server.js中创建了4个端点:
- / home:主页(不是必需的,但我们的登录表单是。)
- / auth:验证访问者(如果失败则返回错误+登录表单)
- / private:我们的受限内容 - 需要登录(有效会话令牌)才能看到此页面
- / logout:使令牌无效并注销用户(防止重新使用旧令牌)
我们故意让server.js尽可能简单:
- 可读性
- 可维护性
- 可测试性(所有辅助/处理程序方法单独测试)
帮助方法:
所有辅助方法都保存在/example/lib/helpers.js中 。两个最有趣/相关的方法是(这里显示的简化版本):
// generate the JWT |
当用户认证时产生了JWT令牌(这随后被发送回客户端在授权头用于后续请求)。
以及:
// validate the token supplied in request header |
以上是检查客户端提供的JWT是有效的?如果有效则向请求者显示私有(“privado”)内容,如果不是则呈现authFail 错误页面。
注意:是的,这两种方法都是同步的。但是,鉴于这些方法都不需要任何 I / O 或 网络请求,因此同步计算它们非常安全。
问题
问:如果我把JWT放在URL或Header中它是否安全?
不安全。除非你使用SSL / TLS来加密连接,明确地发送令牌始将是不安全的(令牌可以被截获并重新使用)。一个天真的 “ 缓解 ”方式是向令牌添加可验证的 “声明”,例如检查请求是否来自相同的浏览器(用户代理), IP地址或更高级的“ 浏览器指纹 ” http://programmers.stackexchange.com/a/122385
解决的办法是下面任一个:
- 使用一次性令牌(在点击链接后就过期失效)
- 不要使用需要高度安全性的url-tokens。(例如:不要向某人发送允许他们执行交易的链接)
url中一次性JWT令牌的用例是:
- 帐户验证 - 当您在网站上注册后通过电子邮件向其发送链接时。 https://yoursite.co/account/verify?token=jwt.goes.here
- 密码重置 - 确保重新设置密码的人员可以访问属于该帐户的电子邮件。 https://yoursite.co/account/reset-password?token=jwt.goes.here
这两者都是一次性令牌的好的使用方式(在点击之后到期)。
问:我们如何使会话无效?
使用应用的用户的设备(手机/平板电脑/笔记本电脑) 被盗。你如何使他们使用的令牌无效?
JWT背后的想法是令牌是无状态的, 它们可以由集群中的任何节点计算,并且在没有(或慢的)请求数据库的情况下进行验证。
将令牌存储在数据库中?
1. LevelDB!如果您的应用程序很小或者您不想运行Redis服务器,则可以通过使用LevelDB获得Redis的大部分好处:http://leveldb.org/
我们可以任意存储有效的DB令牌或者 我们可以存储无效令牌。这两个都需要往返DB以检查是否有效/无效。所以我们更喜欢存储所有令牌,并将令牌的 有效属性从true更新为false。
存储在LevelDB中的示例记录:
"GUID" : { |
我们将通过其GUID查找此记录:
var db = require('level'); |
请参阅:example / lib / helpers.js 验证详细信息的方法。
2. Redis
Redis是存储令牌的可扩展方式。
问:返回访问者(会话之间没有状态)
Cookie存储在客户端上,并在每次请求时由浏览器发送到服务器。如果此人关闭浏览器,则会保留Cookie,因此可以在停止的位置继续操作,而无需再次登录。但是,cookie将在与路径和发布域匹配的所有请求上发送,包括不需要的图像和css。
localStorage 提供了一种更好的机制,用于在浏览器会话期间和之间存储令牌。
1.基于浏览器的应用程序
存储JWT有两种选择:
- 使用localStorage在客户端存储JWT(意味着您需要记住在authorization标题中发送JWT以用于后续的http / ajax请求)
- 将您的JWT存储在cookie中(设置并忘记)
(我们显然更喜欢无cookie方法。但如果做得好,cookie仍然在现代网络应用程序中占有一席之地!)
2.程序(API)访问
访问您的API的其他服务必须将令牌存储在检索系统中(例如:Redis或SQLite用于移动应用程序)并在每个请求时发回令牌。
如何生成密钥?
由于JSON网络令牌(JWT)不使用非对称加密签名,你不能使用ssh-keygen生成使用密钥,您可以轻松使用强密码,例如: https://www.grc.com/passwords.htm。只要它很长且随机。碰撞的可能性(因此有人能够解码您编码的JSON)非常低。如果你将两个强密码(字符串)连接在一起,你将拥有一个128位的ASCII字符串。因此,碰撞的可能性小于宇宙中[url=http://en.wikipedia.org/wiki/Observable_universeMatter_content_.E2.80.94_number_of_atoms]的原子数[/url]。
要使用Node的加密库快速轻松地创建密钥,请运行此命令。
node -e "console.log(require('crypto').randomBytes(32).toString('hex'));"
换句话说,您可以使用RSA密钥,但你并不需要。
您需要记住的主要事项是:不要与不在核心的人(“ DevOps团队 ”)共享密钥,或者在提交给GitHub时突然发布密钥!