JWT相关学习

JSON Web Token(JWT)

JWT是一个开放的标准(RFC 7519),定义了一种紧凑,自包含的方式使用json对象来安全的传递信息。这些信息可以验证和信任,因为是通过HMAC或者RSA等算法做数字签名的。

JWT 特点

  • 体积小,因而传输速度快
  • 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
  • 严格的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为你的应用而定制化
  • 支持跨域验证,可以应用于单点登录。
8c087283.png 7a77b75b.png

以下内容摘自该链接
JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,编码之后的JWT看起来是这样的一串字符:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

由.分为三段,通过解码可以得到:

// 1. Headers
// 包括类别(typ)、加密算法(alg);
{
"alg": "HS256",
"typ": "JWT"
}
// 2. Payload
// 包括需要传递的用户信息,是用户自定义的字段;
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"role": "user"
}
// 3. Signature
// 根据alg算法与私有秘钥进行加密得到的签名字串;
// 这一段是最重要的敏感信息,只能在服务端解密;
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
SECREATE_KEY
)

在使用过程中,服务端通过用户登录验证之后,将Header+Payload信息加密后得到第三段签名,然后将签名返回给客户端,在后续请求中,服务端只需要对用户请求中包含的JWT进行解码,即可验证是否可以授权用户获取相应信息,其原理如下图所示:

5e50c8e9.png
通过比较可以看出,使用JWT可以省去服务端读取Session的步骤,这样更符合RESTful的规范。但是对于客户端(或App端)来说,为了保存用户授权信息,仍然需要通过Cookie或类似的机制进行本地保存。因此JWT是用来取代服务端的Session而非客户端Cookie的方案,当然对于客户端本地存储,HTML5提供了Cookie之外更多的解决方案(localStorage/sessionStorage),究竟采用哪种存储方式,其实从Js操作上来看没有本质上的差异,不同的选择更多是出于安全性的考虑。

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

JWT 安全性

用户授权这样敏感的信息,安全性当然是首先需要考虑的因素。这里主要讨论在使用JWT时如何防止XSS和XSRF两种攻击。

XSS是Web中最常见的一种漏洞,其主要原因是对用户输入信息不加过滤,导致用户(被误导)恶意输入的Js代码在访问该网页时被执行,而Js可以读取当前网站域名下保存的Cookie信息。针对这种攻击,无论是Cookie还是localStorage中的信息都有可能被窃取,但防止XSS也相对简单一些,对用户输入的所有信息进行过滤即可。另外,现在越来越多的CDN服务,让我们可以节省服务器流量,但同时也有可能引入不安全的Js脚本

另外一种更加棘手的XSRF漏洞主要利用Cookie是按照域名存储,同时访问某域名时浏览器会自动携带该域名所保存的Cookie信息这一特征。如果执意要将JWT存储在Cookie中,服务端则需要额外验证请求来源,或者在提交表单中加入随机签名并在处理表单时进行验证。

我在后面的实例中采用将JWT保存在localStorage中的方案,请求时将JWT放入Request Header中的Authorization位。对JWT安全性问题想要了解更多可以参考下面几篇文章:

https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/
https://stormpath.com/blog/jwt-the-right-way/
https://auth0.com/blog/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies/
http://stackoverflow.com/questions/27067251/where-to-store-jwt-in-browser-how-to-protect-against-csrf

签名始终要验证
签名密钥要保证安全
JWT里面不要放敏感数据

If you worried about replay attacks, include a nonce (jti claim), expiration time (exp claim), and creation time (iat claim) in the claims. These are well defined in the JWT Spec

JWT在客户端的保存问题:

  1. cookies
  2. HTML5 web storage (localStorage or sessionStorage)

常见的攻击:

  • cross-site scripting (XSS)
  • cross-site request forgery (CSRF)
  • Man-in-the-middle attack(MITM)

推荐把JWT保存到cookies里面,使用现代框架可以解决CSRF问题。

H5 web storage无法避免XSS攻击,在同一域名下的脚本都可以访问Web storage
cookies使用HttpOnly cookie flag之后javascript无法访问,避免了XSS,还可以设置Secure标记,要求只通过https发送cookies,并且不需要服务端保存状态
但是cookie容易受到CSRF攻击。使用现代框架可以解决CSRF问题。例如AngularJS

JWT localStorage or sessionStorage (Web Storage)

Exchanging a username and password for a JWT to store it in browser storage (sessionStorage or localStorage) is rather simple. The response body would contain the JWT as an access token:

HTTP/1.1 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB",
"expires_in":3600
}

On the client side, you would store the token in HTML5 Web Storage (assuming that we have a success callback):

function tokenSuccess(err, response) {
if(err){
throw err;
}
$window.sessionStorage.accessToken = response.body.access_token;
}

To pass the access token back to your protected APIs, you would use the HTTP Authorization Header and the Bearer scheme. The request that your SPA would make would resemble:

HTTP/1.1

GET /stars/pollux
Host: galaxies.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB

单点登录
Session方式来存储用户id,一开始用户的Session只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:

www.taobao.com
nv.taobao.com
nz.taobao.com
login.taobao.com
所以如果要实现在login.taobao.com登录后,在其他的子域名下依然可以取到Session,这要求我们在多台服务器上同步Session。

使用JWT的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。因此,我们只需要将含有JWT的Cookie的domain设置为顶级域名即可,例如

Set-Cookie: jwt=lll.zzz.xxx; HttpOnly; max-age=980000; domain=.taobao.com

注意domain必须设置为一个点加顶级域名,即.taobao.com。这样,taobao.com和*.taobao.com就都可以接受到这个Cookie,并获取JWT了。

Cookie的缺陷

  1. Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。
  2. 由于在HTTP请求中的Cookie是明文传递的,所以安全性成问题。(除非用HTTPS)
  3. Cookie的大小限制在4KB左右。对于复杂的存储需求来说是不够用的。

一个set-Cookie字段只能设置一个cookie,当你要想设置多个 cookie,需要添加同样多的set-Cookie字段。
服务端可以设置cookie 的所有选项:expires、domain、path、secure、HttpOnly

客户端可以设置cookie 的下列选项:expires、domain、path、secure(有条件:只有在https协议的网页中,客户端设置secure类型的 cookie 才能成功),但无法设置HttpOnly选项。

JWT认证过程

  1. 客户端post表单name,password
  2. 服务端验证用户名和密码,成功后,将用户ID,权限等信息作为JWT Payload的一个属性,生成token
  3. 服务端将token作为该请求Cookie的一部分返回给用户。注意,在这里必须使用HttpOnly属性来防止Cookie被JavaScript读取,从而避免跨站脚本攻击(XSS攻击)
  4. 在Cookie失效或者被删除前,用户每次访问,服务端都可以从Cookie中校验token的有效性
  5. 获取该ID用户的信息,根据请求响应进行响应
  6. 单点登录,将含有JWT的Cookie的domain设置为顶级域名即可
    Set-Cookie: jwt=lll.zzz.xxx; HttpOnly; max-age=980000; domain=.taobao.com
    注意domain必须设置为一个点加顶级域名,即.taobao.com。这样,taobao.com和*.taobao.com就都可以接受到这个Cookie,并获取JWT了。

HTTP无状态,cookie, session

由于HTTP协议本身是无状态的,也就是说服务器只接受客户端的HTTP request,然后返回一个HTTP response。这样用户就每一次都需要输入用户名和密码。

是服务端发送给客户端浏览器的一组数据。
浏览器会保存并在每一次请求中发送给同一个服务端。
通常的作用是用来标识多次请求来至于同一个浏览器-保持用户登录状态
主要用途:
session管理:登录,购物车,游戏分数等任何服务端需要记录的数据
个性化:用户设置,主题,其他设置
记录:记录和分析用户行为

cookies的属性还包含有路径,过期时间等。 路径是为了判断是由服务器端的哪一个url设置了该cookies,对于一个复杂的网站或者cookies中信息比较多时,可以减少传递不必要的信息。过期时间是为了一定的安全性着想。

session

session也是服务器将一部分信息存储在浏览器端,然后浏览器再次请求时携带这一部分信息供服务器查询。
session的实现方式有好几种,只要能起到将所需信息在第二次之后的请求中发送给服务器的功能就可以了。
session的实现方式中有一种是采用 cookies的方式。 在flask中, session可以认为是存储在cookies中的 键为 “session”的一个加密字符串, 然后这个字符串本身又是一个 键值对。

Web服务器

SPA应用Web服务器的功能:
认证与授权
数据验证
数据存储与同步