10点整不整Token

发现最近百度越来越傻×,白洁找不到,技术文章也找不到。

10 Things You Should Know about Tokens

作者好diao,反正我不懂的他都懂,他还写了另外两篇关于 JWT 的 。

然后他决定三连发,多讲讲基于token的认证。那么,黑喂狗...

  1. Token 应该存起来(local/session storage 或者 cookies)
  2. Token 像 cookie 那样过期,不过你能更主动
  3. Local/session storage 不能跨域,用 Cookie 吧
  4. 每个 CORS 请求都应该有预处理请求
  5. 当你要做流处理,用 Token 做许可认证
  6. 处理 XSS 比处理 XSRF 简单
  7. 每个请求都要带 Token,所以精简它。
  8. 如果你保存了 GFW 的机密,那么 Token 应该加密JSON
  9. JWT 在 OAuth 中的应用
 10. Token 不是唯一,你还有许多妹子可以选

1. Token 应该存起来(local/session storage 或者 cookies)

在单页面应用 SPA 里面用 Token,有些人可能会说,如果我刷新浏览器了, Token 怎么办。简单啊:存起来不就好了。在 session storage, local storage 或者在 client 的 cookie。那又不是所有的浏览器都支持 session storage, 所以你可以选择 cookies 来保存它。

然后肯定又会有人说,那不是整一圈又回到 cookie 上来了。也不是啊,这种情况下你是用 cookie 作为存储机制,又没用作Cookie认证机制。所以不会有 XSRF 攻击的啦。

2. Token 像 cookie 那样过期,不过你能更主动

Token 当然也会过期(在 JSON Web Token,JWT 中表现为 exp 属性),要不然有人就是一次登陆到处运行了。Cookies 也有因为同样的原因,会过期。

在 Cookies 的世界,你要让它过期可以用很多种姿势:

  • 关浏览器(Session cookies)
  • 你可以在服务端做过期处理(特别是用框架的时候)
  • Cookies 在没到期的时候也可以一直放在那(关浏览器也不清理)

那 Token 的话,一旦过期了,你会需要弄个新的。你可以实现一个端点来刷新,比如说

  • 验证老 Token
  • 检查用户是否,还在或者说授权被撤销,或者任何原因你想要刷新 Token
  • 发行一个带新日期的 Token

你甚至可以直接规定过两周 Token 失效。

<!-- lang: js -->
app.post('/refresh_token', function (req, res) {
  // verify the existing token
  var profile = jwt.verify(req.body.token, secret);

  // if more than 14 days old, force login
  if (profile.original_iat - new Date() > 14) { // iat == issued at
    return res.send(401); // re-logging
  }

  // check if the user still exists or if authorization hasn't been revoked
  if (!valid) return res.send(401); // re-logging

  // issue a new token
  var refreshed_token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });
  res.json({ token: refreshed_token });
});

如果你需要废掉一个 Token, 在请求的时候,你是需要做些特殊处理。

3. Local/session storage 不能跨域,用 Cookie 吧

如果你设置了一个 Cookie 的域是 .yourdomain.com ,那么它可以被 yourdomain.comapp.yourdomain.com 访问,这样可以很容易的从主域(比如说你的主页什么的)把已登陆的用户重定向到 app.yourdomain.com

Tokens 保存在 local/session storage,换而言之,不能从不同的域(甚至是子域)访问。咋办?

一个选择是,用户从 app.yourdomain.com 访问的时候,你给弄个 cookie 设为 .yourdomain.com

<!-- lang: js -->
$.post('/authenticate, function() {
  // store token on local/session storage or cookie
    ....

    // create a cookie signaling that user is logged in
  $.cookie('loggedin', profile.name, '.yourdomain.com');
});

然后,在 youromdain.com 你可以检查 cookie 的存在性然后重定向到 app.yourdomain.com 如果 cookie 存在的话。然后 token 就可以用在子域了,这之后就是一般流程了(如果 token 还有效,那就用它,如果失效了或者是最后一次登录就弄个新的)。

那这样分开管理的话,就可能发生有 cookie 没 token 的情况,或者其他神奇的事情。这种情况下,用户必须重新登陆。这里我们重申一下,我们一开始就说好的,cookie 不做认证机制,只做存储机制,用来支援跨域存信息。

4. 每个 CORS 请求都应该有预处理请求

有人指出 Authorization header 不是一个 simple header,那么在所有的请求之前先做预处理。

<!-- lang: js -->
OPTIONS https://api.foo.com/bar
GET https://api.foo.com/bar
   Authorization: Bearer ....

OPTIONS https://api.foo.com/bar2
GET https://api.foo.com/bar2
   Authorization: Bearer ....

GET https://api.foo.com/bar
   Authorization: Bearer ....

不过如果你是用 Content-Type: application/json 的话,正好也是这样。所以在很多应用里面都已经在这样做了。

但是有个小问题,OPTIONS 请求本身没有认证头,因此你的框架应该支持针对 OPTIONS 做后处理(注意:貌似 IIS 是有点问题)。

5. 当你要做流处理,用 Token 做许可认证

当使用 cookie 的时候,你可以处理文件下载和一些流内容。但是,在 Token 的世界,如果请求是通过 XHR,你不能这样做。你要解决这个问题,需要生成一个认证请求,像 AWS 那样,比如说,Hawk Bewits 是个很屌的框架,像这样使:

Request:

<!-- lang: js -->
POST /download-file/123
Authorization: Bearer...

Response:

<!-- lang: js -->
ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja

ticket 是无状态的,而且它是建立在: host + path + query + headers + timestamp + HAMC,以及保质期。因此可以用在,比如说,5分钟这样,用来下载。

之后了你会被重定向到 /download-file/123?ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja 。服务端会检查 ticket 是否有效,然后再执行一般的逻辑。

6. 处理 XSS 比处理 XSRF 简单 (我完全不知道说的是啥,反正就是安全方面的)

Cookies 有这样的功能,从 Server 侧设置一个 HttpOnly 标签,然让 Cookie 只能被服务端访问,而 JavaScript 不能。这非常有用,因为可以防止 cookie 内容被客户端代码注入修改,即 XSS 。

因为 tokens 是保存在 local/session storage 或者客户端的 cookie,所以他们是对 XSS 攻击开放的,这值得注意,由于这点,应该尽可能让 tokens 不要太长。

不过如果你开始考虑 cookies 的安全的时候,有一种主要的方式叫 XSRF,事实上 XSRF 非常容易被忽略,一般的小白(比如我),甚至根本就不知道这样的风险,因此许多应用都没有 防御-XSRF 机制。可是所有的人都知道注入攻击。简单的说,如果你对你的输入都没做转码,你就是对 XSS 开放的。因此据我们的经验,防御 XSS 比防御 XSRF 简单多了。再加上 防御-XSRF 不是每个框架都带的,而 XSS 因为大多数模板引擎的转意,都会被处理掉。

7. 每个请求都要带 Token,所以精简它。

每次你要发送 API 请求的时候,都要在 Authorization header 带上 Token。

<!-- lang: js -->
GET /foo
Authorization: Bearer ...2kb token...

vs.

<!-- lang: js -->
GET /foo
connect.sid: ...20 bytes cookie...

根据你存在 token 的信息量,可能更大。另一方面, session cookies 通常只存一个标识符(connect.sid, PHPSESSID, 等.)剩下的内容存在服务器 (或者在内存之类的,如果你只有一个服务器或者数据库在服务群上)。

现在你也可以用 token 来实现 一样的机制。token 保存基本的必须的信息,在服务端可以取得更多信息,在 API 调用的时候。这和 cookies 没什么区别。但是对于你来说有更多的好处,你有充分的控制权,这是你代码的一部分。

<!-- lang: js -->
GET /foo
Authorization: Bearer ……500 bytes token….

然后服务器上是:

<!-- lang: js -->
app.use('/api',
  // validate token first
  expressJwt({secret: secret}),

  // enrich req.user with more data from db
  function(req, res, next) {
    req.user.extra_data = get_from_db();
    next();
  });

值得一提的是,你也可以用 session 来保存完全的信息。不单单是标识符。但不是所有的平台都支持。对于 node.js ,你可以用 mozilla/node-client-sessions

8. 如果你保存了 GFW 的机密,那么 Token 应该加密JSON

token 上的令牌可以防止它被篡改,TLS/SSL 可以防止 middle attacks。不过如果内容中带有一些敏感信息 (比如说 SSN,之类的话),你是需要加密他们的。JWT 是遵从 JWE 规范的,但是许多库并没实现 JWE ,因为最简单的,你应该给他们用 AES-CBC 加个密,像下面这样:

<!-- lang: js -->
app.post('/authenticate', function (req, res) {
  // validate user

  // encrypt profile
  var encrypted = { token: encryptAesSha256('shhhh', JSON.stringify(profile)) };

  // sing the token
  var token = jwt.sign(encrypted, secret, { expiresInMinutes: 60*5 });

  res.json({ token: token });
}

function encryptAesSha256 (password, textToEncrypt) {
  var cipher = crypto.createCipher('aes-256-cbc', password);
  var crypted = cipher.update(textToEncrypt, 'utf8', 'hex');
  crypted += cipher.final('hex');
  return crypted;
}

当然你可以像 #7 那样,把重要的信息存在数据库。

Update: Pedro Felix 指出 MAC-then-encrypt 对于 Vaudenay-style 攻击的脆弱性,于是作者更新了他的代码。

9. JWT在OAuth中的应用

Token 通常用在 OAuth上。OAuth2 是解决标识符授权的另一种认证协议。提示用户确认授权访问他/她的数据,然后认证服务器返回一个 access_token 作为访问API时的授权用户标记。

通常这些 token 是不透明的,被称之为 bearer tokens 。被用随机字符串存在服务器的哈希表上(db, cache, 之类.),并附带有时效标记,授权范围 (比如,访问朋友圈的权限)以及授权用户。然后当访问 API 的时候,token 被发送到服务端,服务端查找哈希表,解析内容,确认访问权限(是否过期?token对于 API 所需要的权限是否有正确的授权?)。

和我们之前讨论不同的是,原来的 signed token (比如 JWT)是无状态的。他们不需要被存储在哈希表中,是一个更轻量的解决方法。OAuth2 没有决定 access_token 的格式,因此你可以从授权服务器返回一个包含了授权范围/权限和有效期的 JWT。

10.Token 不是唯一,你还有许多妹子可以选

想当年,我帮一家大公司实现一个基于 token 的架构。这公司有 100.000+(是100多号人么) 雇员,以及超级多的保密资料。他们希望可以做一个集团内集中管理"授权认证"系统。想一下这样的情况,"用户X可以查询W国的Z医院的临床项目Y的名字和字段"。这种细粒度的授权,不可能很快解决的,不单止技术,还有管理上的问题。

  • Token 承载过多内容
  • 应用/API会很复杂
  • 授权者很难掌控这一切

我们最终在信息架构上确保了授权范围和权限。结论是:抵制把所有的东西都放到 token 的诱惑,在开始用这种方式之前,做一些分析和取舍。


免责声明:去看作者的原文吧。

转载于:https://my.oschina.net/ilivebox/blog/279408

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值