安全开发之 token 那些事

本文作者:晚风(信安之路作者团队成员)

在开发网络应用时,不管是移动端的 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">
  ...
</form>

在表单提交上来时先检查接收到的 token 是否与 session 中的 token 相等,相等即可证明请求是来自用户自己,不相等则该请求很可能并非来自用户本身,很可能用户遭到了 CSRF 攻击。

方案二:cookie 中 token+ 表单页面 token

在用户登录成功后服务器生成 token,一份同上存入表单页面的隐藏域中,一份存入用户 cookie,以 PHP 为例如下:

setcookie("token",generateToken(),time()+3600,'','','',true);

同样的,当服务端接收到请求时,比对 cookie 中的 token 和表单中的 token 是否相等,相等则合法,反之非法。这种方案的优势在于服务器端可以不存放用户的登录状态,节约了服务器的资源,也算是顺应了 http 的无状态。

2、前后端分离开发

使用前后端分离的开发模式是较为新颖的开发模式。这种开发模式一般是前端与后端先协商好一份 Restful API 文档,标明请求的地址、格式、类型等,然后各自对照着这份 api 文档同时进行开发,提升了效率。这种开发模式在目前流行的单页应用(SPA)中使用较多。在这种应用中可以不使用 session 会话来维持客户端与服务器的通信。转而只用 JWT(Json Web Token)来实现身份认证。

https://jwt.io/introduction/

单页应用为了维护其良好的用户体验,发送请求的方式由传统的 form 表单提交改为了使用 AJAX/Fetch 传输数据,实现页面无刷。用户在登录成功接收到 token 后可以将 token 存在内存中,也就是可以存在一个 JS 全局变量里,也可以存在 LocalStorage 中,唯一的区别是后者可以实现自动登录而前者不可以。每次发送请求时将 base64 编码后的 token 添加到 header 里的 Authorization 中发送给服务器:

//ajax
$.ajax({
   type: 'POST',
   url: '/api/datapost',
   data: {
       title: '...',
       content: '...'
  },
   headers: {
       Authorization: 'dG9rZW5IZXJl'
  },
   success: function(res) {
       do(res);
  }
})

//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 方案仅供参考。

原文发布于微信公众号 - 信安之路(xazlsec)

原文发表时间:2018-07-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏杨建荣的学习笔记

Datapump数据迁移前的准备工作(r9笔记第31天)

其实对于Datapump迁移而言,如果参与过XTTS,OGG,Veritas SF,外部表增量等迁移方式的话,会发现Datapump还是很简单清晰的,一个优点...

28230

如何在Linux上搭建Terraria 游戏服务器?

Terraria是一款二维沙盒游戏,类似于Minecraft(我的世界),允许玩家在开放的世界中探索,构建和战斗。2015年,Terraria开发者宣布支持Li...

83730
来自专栏做全栈攻城狮

C#实现动态网站伪静态,使seo更友好

本教程将使用Visual Studio 2013手把手教你实现webform动态页面的伪静态。本教程配套的C#源码工程可通过我的github下载。地址:http...

15640
来自专栏菩提树下的杨过

spring cloud: 使用consul来替换config server

上一篇提到了,eureka 2.x官方停止更新后,可以用consul来替代,如果采用consul的话,其实config server也没必要继续使用了,cons...

24030
来自专栏张善友的专栏

使用 MDT 2010 进行可伸缩部署

最近半个月在实施学习Windows 7自动化部署过程中的一个总结分享。Microsoft Deployment Toolkit 2010是微软最新一代部署工具,...

39450
来自专栏JavaEdge

操作系统之设备管理一、I/O管理概述二、I/O硬件组成三、I/O控制方式(重点)四、I/O软件组成五、I/O相关技术六、I/O设备的管理七、I/O性能问题

2.3K60
来自专栏IT 指南者专栏

基于 Hexo + GitHub Pages 搭建个人博客(三)

打开 themes 目录下的 next 主题配置文件,找到 Wechat Subscriber 标签,将该标签下的配置改成如下形式:

50540
来自专栏程序员宝库

webpack 4 升级指北

2018年2月25日,刚过完年webpack就给了一个加班红包。webpack4经过1个月的缓冲期,终于发布了正式版,那么抛给广大开发者的问题又来了,我是不是要...

75770
来自专栏FreeBuf

史上最全Linux提权后获取敏感信息方法

在本文开始之前,我想指出我不是专家。据我所知,在这个庞大的区域,没有一个“神奇”的答案。下面是一个混合的命令做同样的事情,在不同的地方,或只是一个不同的眼光来看...

87160
来自专栏林冠宏的技术文章

Go 自带的 http/server.go 的连接解析 与 如何结合 master-worker 并发模式,提高单机并发能力

34250

扫码关注云+社区

领取腾讯云代金券