JWT(Json web token)认证介绍

facebook-card.png

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

要更好的使用 JWT ,那么不得不了解 Token ,翻译过来就是令牌,,字面意思就代表它的意思了,它就是一个令牌,通俗的讲就是暗号。它表现形式是一串字符,通常用于鉴别是否有无权限(鉴权凭证)

例如:一个典型用户在登陆网站例子,传统流程是这样的

  • 用户输入账号密码,点击登陆
  • 服务端验证通过,为用户设置 session,返回给客户端,由客户端存储在本地
  • 在之后每一次访问网站,客户端都会携带该 cookie(session_id 存储是借助 cookie 机制的) 访问网站
  • 服务端会对此 cookie 进行验证,通过就返回数据
  • 若验证不通过,就说明当前是处于未登录状态
    • session 已过期
    • 客户端清除了 cookie
    • 等等一些因素

这样做的缺点有几点:

  • 为用户设置 Session 会大量占用服务器资源
  • CSRF 跨站请求伪造,简单说,用户登陆了一个网站,在本地存储了 cookie,这时候访问另外一个网站(恶意网站),这个网站呢,此时可以伪装成用户,发起恶意请求到用户已授权的网站,这样达到了攻击的目的(可窃取一切可能的信息,钱财…)
  • CORF 跨域资源共享,数据在不同移动设备不能共享,而且涉及到跨域的问题,使用 ajax 实现起来也困难

若使用 Token 呢?同样例子

  • 用户登陆
  • 服务端验证通过,生成一串字符(Token),发送给客户端保存在本地
  • 后续任何访问操作都携带 token 访问服务端

看着流程没多大变化,那么 Token 优势在哪里呢?

  • 生成的字符串不占用服务器资源
  • 避免 CSRF 攻击
  • 数据共享,在移动端是不支持 Cookie,而 token 可以存在网站任何地方

通过上面的例子可以得知,token 就是一个凭证,通过这个凭证能做些拥有权限范围的操作,而没有凭证,那么将是无效的操作。

token 既然是一串字符,那么实现它的方式就多种多样。不过,token 也有一套规范,在 RFC 7519 为 token 定义了授权规范。

JWT的组成

一个JWT实际上就是一个字符串,它由三部分组成,头部载荷签名

jwt_anatomy.png

头部(Header)

JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

{
  "typ": "JWT",
  "alg": "HS256"
}

在这里,我们说明了这是一个JWT,并且我们所用的签名算法(后面会提到)是HS256算法。

对它也要进行Base64编码,之后的字符串就成了JWT的Header(头部)。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

载荷(Payload)

我们先将上面的添加好友的操作描述成一个JSON对象。其中添加了一些其他的信息,帮助今后收到这个JWT的服务器理解这个JWT。

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

这些定义都可以在标准中找到。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到 JWT 的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串。

var base64url = require('base64url')
var header = {
    "from_user": "B",
    "target_user": "A"
}
console.log(base64url(JSON.stringify(header)))
// 输出:eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

签名(Signature)

JWT 的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要 base64 加密后的 header 和base64加密后的 payload 使用 . 连接组成的字符串,然后通过header中声明的加密方式进行加盐 secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用 . 连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

签名的目的

最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样的输出的概率极其地小。所以,我们就把“不一样的输入产生不一样的输出”当做必然事件来看待吧。

所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。

服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部中已经用alg字段指明了我们的加密算法了。

如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。

信息会暴露?

是的。

所以,在JWT中,不应该在载荷里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的Username。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。

但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

应用

通常会在请求头部加上 Authorization,同时用 Bearer 标识

header{
    "Authorization" : 'Bearer ' + token
}

除了上面的方法,还有其它的一些方法

GET 参数

https://huasio.com/?token=.................

POST 参数 ……

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:

JWT-AUTH.png

总结

优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议

JWT的适用场景

我们可以看到,JWT适合用于向Web应用传递一些非敏感信息。例如在上面提到的完成加好友的操作,还有诸如下订单的操作等等。

其实JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。在下一次的文章中,我将为大家系统地总结JWT在用户认证和授权上的应用。


参考:

微信打赏微信打赏
支付宝打赏支付宝打赏

评论: