关于Tokens你需要知道的10件事
本文最后更新于 3151 天前,其中的信息可能已经有所发展或是发生改变。

关于Tokens你需要知道的10件事

——进一步探讨基于Token认证的一些常见问题

原文链接:Here

原作者:Matias Woloski

几周前我们发表了一篇短文《cookies与tokens在单页应用中的对比》(主要以AngularJs应用为例)。社区里对这个话题很感兴趣,于是我们接着发表了第二篇《在socket.io等实时框架中基于Token的认证》。趁着大家对这个话题还保持着热情,我们决定再写一篇文章进一步探讨基于Token认证的常见问题。我们开始吧~

1.Tokens需要保存在Local Storage、Session Storage或Cookies中

在Tokens被应用于单页应用的背景下,有人提出了问题:在浏览器中刷新页面时,token会发生什么变化。答案很简单:你需要将token保存在Local Storage、Session Storage或客户端内置的Cookies中,在浏览器不支持Session Storage的情况下,它会被polyfills为cookies。

你或许想问“但如果我将token保存在cookie的话,岂不是很危险”。其实不然,这种情况下,你使用的cookies只是作为存储机制而非验证机制(即是说,web框架不会使用cookie来验证用户,因此不存在XSRF攻击的危险)

2.Tokens像Cookies一样会过期,但你有更多的控制权

Tokens具有生命周期(在JSON Web Tokens中由exp属性控制),否则用户就可以登录一次却永远无需认证了。Cookies由于相同的原因也具有生命周期。

在Cookies中,如下不同情况生命周期也不一样:

1.当关闭浏览器时,Cookies会被销毁(如Session Cookies)

2.你也可以实现一个服务端检查机制(通常由Web框架帮你完成),你可以设置生命周期或滑动窗口生命周期。

3.Cookies在一段时间内可以是永久的(即使关闭浏览器也不会被销毁)

在Tokens中,一旦过期,你只需要获取一个新的token。你可以写一个端点来更新token:

1.验证旧的token

2.检查用户是否还处于登录状态或者在访问你的网站

3.产生一个重新续时的新token

你甚至可以将token生成的时间写入其中,并在大约两周后强制用户重新登录。

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 });
});

3.Local/Session Storage无法跨域,请使用Cookies作标记

如果你将cookie的作用域名设置为.yourdomain.com,那么它可以在yourdomain.comapp.yourdomain.com中被访问。如果用户在主域登录却被重定向到app.yourdomain.com,要验证cookie也是很方便的。

Tokens存储在local/session storage中,这意味着不同域名之间是无法相互访问的(即使是子域名)。那么你该怎么做呢?

其中一种做法是,当用户在app.yourdomain.com中验证完毕后,你生成一个token并设置一个作用域名为.yourdomain.com的Cookie标记用户已经登录。

$.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');
});

然后,你可以在yourdomain.com验证cookie的存在并重定向到app.yourdomain.com。而Token可以在app的子域中使用(如果token依然合法)。

可能会出现cookie存在而token已经被删除的情况。这种情况下,用户必须重新登录。这里需要强调的是,正如我们之前所说,我们并不将cookie当作验证机制,而仅仅把它作为能够跨域保存信息的存储机制。

4.每个CORS请求都会先发一个预请求

有人指出,Authorization报头并非一个简单的报头,因此在向特定URLs发送请求前都需要发送一个预请求。

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请求自身并没有Authorization报头信息,所以你的web框架需要对OPTIONS和后续的请求作一些处理(提示:微软的IIS因为某些原因在这方面存在一些问题)。

5.当你处理流媒体时,请用token获取签名请求

在使用cookies时,你可以轻易地触发文件下载和一些文本流。然而,在tokens中,是通过XHR来完成请求的,你无法依赖于此。解决的办法就是像AWS那样生成一个签名请求,例如,Hawk Bewits就是一个支持该方法的优秀框架:

请求:

POST /download-file/123
Authorization: Bearer...

回应:

ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja

其中,ticket是无状态的。它通过URL: host + path + query + headers + timestamp + HMAC来生成,并带有过期时间。所以它能在一段时间内(比如5分钟)被用来下载文件。

你会被重定向到/download-file/123?ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja。服务器将验证ticket是否合法并完成接下来的业务操作。

6.比起XSRF,处理XSS较为容易

Cookies具有允许设置HttpOnly标记的特性,它可以只允许服务器访问而非JavaScript。这很有用,因为它保护Cookies不被客户端代码注入攻击(XSS)。

由于tokens被存储在local/Session Storage或者客户端内置的Cookie中,这容易遭受XSS攻击并被攻击者获取到token。这是个令人担忧的问题,因此你需要将tokens的生存时间设置得短一点。

Cookies层面上,一种主要的攻击是XSRF。真实情况是,XSRF是最让人忽略的攻击之一。普通的开发者可能甚至不了解这种风险,因此很多应用缺少反XSRF攻击的机制。然而,每个人都知道注入是什么。简单地说,如果你允许在不转义的情况下渲染用户输入的内容,那么你的应用就暴露在XSS攻击之下了。根据我们的经验,防范XSS比XSRF容易。因为并非所有的web框架都内置了反XSRF机制,而在绝大多数支持转义语法的模板引擎中,防范XSS却是容易得多。

7.每次请求都需要发送token,请注意它的大小

当你每发送一次API请求,都需要将token附加到Authorization头部中发出去。

GET /foo
Authorization: Bearer ...2kb token...

和Cookies的比较

GET /foo
connect.sid: ...20 bytes cookie...

token的大小取决于你将多少信息存在里面了,这可能会很大。相比起来,Session Cookies只保存一个标记(connect.sid,PHPSESSID等等),主体内容被保存在服务端(仅有一台服务器就保存在内存,在服务器群中则保存在数据库)。

现在,你完全可以实现一个类似tokens的机制。这个token拥有你需要的基本信息,在服务端中根据每次API的调用,你都可以为token补充更多的信息。Cookies的确也能做到这样,但不同的是tokens有一个好处,你可以完全控制它,毕竟它是你代码的一部分。

GET /foo
Authorization: Bearer ……500 bytes token….

在服务端中:

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保存在Cookie中(而不仅仅是一个标记)。但并不是所有的Web平台都支持这么做,举个例子,在Node.js中,你可以使用mozilla/node-client-sessions

8.如果存储敏感信息,请对token加密

Token的签名能够防止信息被篡改,TLS/SSL能够防止中间人攻击。但如果包含了用户的敏感信息(身份证号等),你就需要对它进行加密。JWT(JSON Web Tokens)用JWE(JSON Web Encryption)作为规范,但大多数类库都还没有实现JWE,所以最简单的做法就是像下面那样使用AES-CBC模式进行加密。

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那样,将敏感信息保存在数据库中。

9.JSON Web Tokens可以在OAuth中使用

Tokens往往和OAuth联系在一起。OAuth2是一种解决身份认证授权的协议。经过用户同意授权访问自己的数据后,认证服务器会返回一个access_token,这样可以使用用户的身份访问对应的APIs。

通常这些tokens是不透明的。他们被称之为bearertokens,并且以随机字符串保存到某种类型的哈希表中,并存储在服务器里(数据库、缓存等)。内容包括过期时间、请求范围(例如访问好友列表)以及授权的用户。然后当API被调用时,token会被发送给服务器,服务器会在哈希表中查找信息并开始验证(比如token是否已过期,是否超出请求范围)。

这种token和我们一直讨论的签名token(如JWT)的主要区别是,后者是无状态的,它们并不需要存储在哈希表中,因此它是一种更轻量级的方法。OAuth2并没有规定access_token的格式,所以你可以返回一个经授权服务器包含的带有“请求范围、权限列表和过期时间”的JWT。

10.Tokens不是万能的,请仔细考虑授权使用场景

几年前,我们为一家大公司实施开发基于token的架构。这是一个有大量信息需要被保护的拥有超过10万名员工的公司。他们想要实现一个基于“身份验证和授权”的集中式管理组织系统。试想想“用户X可以读取W地区里Z医院的临床试验Y的ID和名称”的应用场景。这种细粒度的授权,你可以想像,不管是在技术还是管理上,都是很难处理的。

  • Tokens会变得很大
  • 你的apps/APIs会变得很复杂
  • 不管是让谁来授予权限都是很难进行下去的

站在信息架构的角度上,为确保创建合理的作用范围和权限,我们放弃了这个工作。

结论:要抵制把一切东西都转换成tokens的诱惑,在使用这种方式时请务必先做好各种分析。

 

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇