摘要.
在开发网络应用时,不管是移动端的 APP 也好,还是 web 端 APP 也好,只要有用户群体存在,都绕不开身份认证这个话题,选择一种好的身份认证方法常常在应用安全中起到了至关重要的作用。目前用的最多的就是使用 “token” 认证用户的身份。
本文主要讲述了你不知道的关于 token 的那些事,以及在目前常见的应用中增加 token 认证的方案。
token 的作用
token 是什么?简单来说 token 就是在客户端与服务器之间传输的一段字符串。
说到 token 的作用,那这里不得不提一下 CSRF 攻击。CSRF 全称 “跨站请求伪造攻击”。假设一个用户登录一个银行网站,此时银行网站将用户的登录状态保存在了浏览器的 cookie 中,每当用户访问这个银行网站的不同页面时,浏览器会自动带上 cookie 中用户的登录状态,服务器以此来判断用户登录与否,并根据用户的登录状态响应不同的结果。
此时,攻击者写了一个恶意页面,内含一个指示银行网站从用户账号向攻击者账号转钱的请求,并诱使用户访问这个攻击者写的恶意页面。一旦用户访问了这个恶意页面,该恶意请求将自动带着
cookie
中用户的登录状态被发送到银行网站的服务器上,银行服务器认为这个请求是用户自己发出的,就执行了该请求,从用户的账号向攻击者的账号转了相应数额的钱,给用户造成了损失。
CSRF 攻击大体上就是这个样子,不过 CSRF 不是本文讲述的重点,有兴趣可以自行了解 CSRF 的细节部分。
token
就是用来区别请求是来自用户本身还是他人伪造的一个好办法。当用户在登录时,服务器生成一个 token 发送给客户端,客户端把这个 token
存在内存中或者本地,每次请求都带上这个 token,服务器接收到这个 token 并验证合法性,合法即继续执行请求,非法即拦截请求,不予执行。
由于浏览器的同源策略的限制,攻击者的页面无法跨域得到用户页面接收到的
token,所以攻击者的请求肯定是无法给出合法的 token 的(排除 token 被盗的可能,token
被盗不是本文讨论的范畴),由此服务器可以判断请求到底是用户自己发出的,还是以用户的名义被伪造发出的。从而防范 CSRF 攻击。
token 在开发中的实践
1、前后端混合开发
使用前后端混合开发模式是较为传统的开发模式。一般是后端写完功能让前端写样式,前后端共同维护着同一个页面。在这种应用中,session
会话就挑起了客户端与服务端通信的大旗。请求一般以 form 表单的形式发送给服务器。在这种应用中加上 token
进行身份验证常见的有两种方案。
方案一:服务端 token+ 表单页面 token
在用户输入正确的用户名和密码登录成功后,由服务器生成 token,一份存入 session 中,以 PHP 为例:
$_SESSION['token'] =generateToken();
一份存入页面中的表单,在页面上所有的表单中加入一个存放 token 的隐藏域:
<formaction=""method="POST">
...
<inputtype="hidden"name="token"value="tokenHere">
...
//fetch
fetch('/api/datapost',{
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": 'dG9rZW5IZXJl'
},
body: JSON.stringify(
{
title: '...',
content: '...'
}
)
}).then(res=>do(res))
.catch(e=>handle(e))
服务器解密验证 headers 中的 JWT,得到身份数据。整个流程如下:
大家可以想想为什么前两种方案都需要验证两个 token 是否相等来判断 token 的合法性而这里不需要。
这是因为攻击者如果要利用
CSRF,构造一个包含恶意请求的页面,无论 GET 还是 POST 还是别的请求类型,由于同源策略的限制,请求只能由构造 form
表单发出,AJAX
是不支持跨域发送请求的(除非服务器开启跨域支持,如果服务器开启跨域,开发者需要严格限制请求的来源,对不信任的来源不予响应),而通过表单发送的请求是没法添加自定义的
header 头的,也就是说攻击者是发不出 header 中带有 token 的请求,所以我们可以以此来进行身份认证。
这种方案的优势在于服务器保持无状态,不需要维持用户的登录状态,给服务器节约了资源。而且在一些无法使用 cookie 的场景下也适用。
token 的生成方法
其实
csrf token 就是一段随机值而已,它的实现方法因人而异,不同的公司可能有不同的标准,可以使用标准的 JWT
格式,也可以是内部约定的实现方法,但总的来说要满足随机性,不能轻易被别人预测到;字符数不能太长也不能太短,太短容易被碰撞出来,太长给传输带来不便,耗费资源影响速度。下面分别以
PHP 和 JAVA 为例
PHP:使用 uniqid() 方法生成随机值,开启第二个参数增加一个熵,使生成的结果更具唯一性,应对高并发
functiongenerateToken() {
returnmd5(uniqid(microtime() . mt_rand(),true));
}
JAVA: 使用 java.util 包下面的 UUID 类中的 randomUUID() 方法
publicstaticStringgenerateToken()
{
returnUUID.randomUUID().toString().replace("-", "");
}
当然了,具体的实现方案还要按照各自的业务需求来,这里只是介绍了几种常见的 token 方案仅供参考。
转自公众号:信安之路 https://zhuanlan.zhihu.com/p/40588156 https://zhuanlan.zhihu.com/p/40588156