如何存储这些令牌。如果你正在构建一个web应用程序,你有两种选择:
- HTML5 Web Storage (localStorage或sessionStorage)
- Cookies
比较这两个,假设我们有一个虚构的AngularJS或单页应用(SPA)叫做galaxies.com,登录路由(/token)验证用户身份返回一个JWT。访问你的SPA其他API端点服务,客户端需要传递一个有效的JWT。
单一页面应用程序的请求将会类似:
HTTP/1.1
POST /token
Host: galaxies.com
Content-Type: application/x-www-form-urlencoded
username=tom@galaxies.com&password=andromedaisheadingstraightforusomg&grant_type=password
你的服务器的响应会根据你是否使用cookie或Web Storage而变化。为了比较,让我们看看如何两者兼顾。
JWT localStorage或sessionStorage(Web存储)
用username和password交换JWT存储在浏览器存储中(sessionStorage或localStorage)是相当简单的。响应正文将包含JWT作为一个访问令牌:
HTTP/1.1 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB",
"expires_in":3600
}
在客户端,你可以将这个令牌存储在HTML5 Web存储中(假设我们有一个成功的回调函数):
function tokenSuccess(err, response) {
if(err){
throw err;
}
$window.sessionStorage.accessToken = response.body.access_token;
}
回传访问令牌到你受保护的API,你将使用HTTP Authorization Header和Bearer组合。请求你的SPA将会像:
HTTP/1.1
GET /stars/pollux
Host: galaxies.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB
JWT Cookie Storage
用username和password交换JWT存储在cookie中也很简单。响应使用Set-Cookie HTTP头:
HTTP/1.1 200 OK
Set-Cookie: access_token=eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB; Secure; HttpOnly;
回传访问令牌到你同一领域受保护的API,浏览器将自动包括cookie的值。请求你受保护的API将类似于:
GET /stars/pollux
Host: galaxies.com
Cookie: access_token=eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB;
那么,有什么区别呢?
如果你比较这些方法,两者都获取一个JWT到浏览器。两者都是无状态的,因为你的API需要的所有信息都在JWT中。都是简单的传递备份到你受保护的API。区别在于介质。
JWT sessionStorage和localStorage的安全性
Web存储(localStorage/sessionStorage)可以通过同一域上JavaScript访问。这意味着任何在你的网站上运行的JavaScript都可以访问Web存储,因为这样容易受到跨站点脚本(XSS)攻击。XSS,简而言之,是一种漏洞,攻击者可以注入在页面上运行的JavaScript。基本的XSS攻击试图通过input表单注入JavaScript,攻击者将<script>alert('You are Hacked');</script>放入表单,以查看其是否能被浏览器运行,并能被其他用户查看。
为了防止XSS,普通的响应是避开和编码所有不可信的数据。但这远不是故事的全部。2015年,现代的web应用程序使用托管在CDN或者外部基础设施上的JavaScript。现代的Web应用程序使用第三方JavaScript库,用于A/B测试、 funnel/market analysis和广告。我们使用像Bower这样的包管理器导入别人的代码到我们的应用程序。
如果你使用的脚本中有一个被盗用了怎么办?恶意的JavaScript可以嵌入到页面上,并且Web存储被盗用。这些类型的XSS攻击可以得到每个人的Web存储来访问你的网站,没有他们的知识。这可能是为什么许多组织建议不要在Web存储中存储任何有价值或信任任何Web存储中的信息。这包括会话标识符和令牌。
作为一种存储机制,Web存储在传输过程中不强制执行任何安全标准。无论谁读取和使用Web存储,必须进行尽职调查以确保他们总是通过HTTPS发送JWT,绝不用HTTP。
JWT Cookie存储的安全性
Cookies,当使用带有HttpOnly的cookie标志时,通过JavaScript是无法访问的,并且对XSS是免疫的。你还可以设置安全的cookie标志来保证cokie仅通过HTTPS发送。这是过去利用cookie存储令牌或会话数据的主要原因之一。现代开发人员不愿使用cookie,因为它们通常要求状态被存储在服务器上,从而打破RESTful的最佳实践。如果你在cookie上存储JWT,cookie作为存储机制不用将状态存储在服务器上。这是因为JWT封装了所有服务器需要服务的请求。
然而,cookies容易受到不同类型的攻击:跨站点请求伪造(CSRF)。CSRF攻击是当一个恶意网站,电子邮件或博客导致用户在当前已验证用户的可信站点上的web浏览器中,执行一个有害的动作时发生的攻击。这是一个浏览器如何处理cookies的漏洞。cookie只能被发送到的允许的域中。默认情况下,这个是最初设置cookie的域。请求将发送一个cookie无论你在galaxies.com或hahagonnahackyou.com。
CSRF的工作试图引诱你到hahagonnahackyou.com。该网站将有一个img标记或JavaScript来模拟一个表单post到galaxies.com,并试图劫持你的会话,如果它仍然有效,就修改您的帐户。
例如:
<body>
<!– CSRF 用img标签 –>
<img href="http://galaxies.com/stars/pollux?transferTo=tom@stealingstars.com" />
<!– 或用一个隐藏表单post–>
<script type="text/javascript">
$(document).ready(function() {
window.document.forms[0].submit();
});
</script>
<div style="display:none;">
<form action="http://galaxies.com/stars/pollux" method="POST">
<input name="transferTo" value="tom@stealingstars.com" />
<form>
</div>
</body>
两者都将发送cookie为galaxies.com,并可能导致未经授权的状态改变。使用同步令牌模式,CSRF是可以预防的。这听起来很复杂,但是所有现代的web框架都支持这个。
例如,AngularJS有一个解决方案去验证,只能由你的域才能访问cookie。直接来自AngularJS文档:
当执行XHR请求时,$http服务从cookie中读取令牌(默认,XSRF-TOKEN)并将其作为一个http头(X-XSRF-TOKEN)。因为只有JavaScript运行在你的域才可以读取cookie,你的服务器可以确信XHR来
自你的域中运行的JavaScript。通过包含一个xsrfToken JWT claim,你可以让这个CSRF保护无状态。
{
"iss": "http://galaxies.com",
"exp": 1300819380,
"scopes": ["explorer", "solar-harvester", "seller"],
"sub": "tom@andromeda.com",
"xsrfToken": "d9b9714c-7ac0-42e0-8696-2dae95dbc33e"
}
如果我使用Stormpath SDK for AngularJS,获得无状态CSRF保护没有开发工作。
利用你的web应用程序框架的CSRF保护,使得cookies存储JWT绝对可靠。CSRF也可以从你的API中通过检查HTTP Referer和原始header部分阻止。CSRF攻击将有与应用程序无关的Referer和原始heade。
虽然他们更安全的存储你的JWT,cookies可能导致一些开发商头痛,这取决于你的应用程序的运转是否需要跨域访问。只是知道cookies有附加属性(域名/路径),可以对其进行修改,从而允许你指定cookie的发送位置。使用AJAX,你的服务器端也可以通知浏览器证书(包含Cookies)是否应该随着CORS请求发送。
结论
JWTs是一个很棒的身份验证机制。它们给你一个结构化的方式声明用户和它们可以访问的内容。可以对它们进行加密和签名来防止在客户端进行篡改,但魔鬼在于细节和你把它们存放在哪里。
Stormpath建议你把JWT存储到Web应用程序的cookies中,因为他们提供的额外的安全性,防止现代web框架CSRF的简单性。HTML5 Web存储很容易受到XSS攻击,有一个更大的攻击面积,一个成功的攻击会影响所有应用程序的用户。