likes
comments
collection
share

一种安全HTTP请求Token设计和实现

作者站长头像
站长
· 阅读数 57

需求和构想

我们都知道在业务应用开发中,使用HTTP协议的一个主要的问题就是会话管理,因为HTTP请求,本身是无状态的,但业务的需要,又要求在不同的HTTP请求之间,建立某种逻辑关系(如认为它们是同一个用户发起的),这就称为会话管理。针对这个矛盾,一般可以采用三种技术路线,来实现这一需求,它们是Cookie,Session和Token。

  • Cookie:

Cookie是HTTP协议标准,它允许服务器在请求时,使用SetCookie命令在响应信息头中,向客户端浏览器传输一些信息;在下次请求时,浏览器就需要按照规范,在请求时的http头信息中自动附加这些信息便于服务器进行后续处理,这样就在不同的请求过程中建立了联系;比如如果cookie里包括了用户信息,就可以认为是同一个用户在发起这些请求了。

显然Cookie是有一些问题的,没有HTTPS的请求过程,这些信息可能会暴露或者被伪造;虽然可以控制cookie的范围(通常通过域名),但显然很多请求不需要这些信息(如图片、脚本文件等),但浏览器也会自动加上cookie,也是不小的开销,所以现在一般不会用于有关权限的会话管理。

  • Session

Session被认为是服务端会话管理。但笔者认为它其实不是HTTP标准协议的一部分。按照笔者的理解,一般的session技术是这样实现的:

用户登录完成后,服务端维护一个Session列表(数组,可以是在内存、Redis或者数据库),比如为登录创建一个sessionid, 对应的一个列表元素中,就用用户信息;然后将这个sessionid写入cookie;当用户再次请求时,会带有这个sessionid,服务端使用这个id来从会话列表中获取对应的用户信息来开展业务。

这样可以看到,本质上Session就是cookid的扩展应用,只不过用sessionid的方式实现,实际用户信息保存在服务端了而已,虽然安全性略有提升,但并没有本质上解决问题。如果攻击者拿到sessionid,他就有可能伪造请求,不需要知道用户信息,也可以进行业务操作。此外,服务端需要保存和维护会话信息,Cookie的自动提交,这些都是性能上的开销和代价。

  • Token

Token的本意是"令牌",在网络技术上的意思是在请求过程中,附加一些认证和安全方面的信息,接收方可以使用信息安全方面(如密码学)的手段,对这些信息进行检查和验证,来保证访问过程和数据的安全性。

通常,在HTTP应用实现中,Token在技术通常不使用HTTP提供的Cookie等功能,而是自行构造请求和内容,并通过自定义的密码学操作,来保证请求过程的逻辑关系和安全性,其本质就是请求过程的可控性和安全性。

特别是在前后端分离的实现模型和场景中,笔者觉得使用Token来管理会话是更加合适的,本文的主要内容也是讨论Token机制的设计与实现。

Token结构和内容设计

基于上面的讨论, 我们了解到,Token技术其实是一个相对独立,并且可自定义和扩展的技术,除用于客户端请求(AJAX)之外,也可以使用到后端的通讯过程。并且,设计和实现一个良好的token机制,对于系统通讯和运行的安全性和效率,都有比较大的促进。

那么,在一个真实的应用当中,我们如何设计和实现一个比较好的Token机制呢? 先来看看笔者认为一个良好的Token机制,需要综合考虑安全性、效率、可扩展性、互操作性和可开发性等方面,如应该有包括以下特点:

  • Token中需要包括请求方和服务方的相关信息(特别是后端间通信)
  • Token中包括网络相关信息,如请求方的IP地址,可以检查作为安全机制
  • Token中需要包括token的过期时间信息,可以检查作为安全机制
  • Token中实际内容(载荷)是可扩展的,并可选加密或者不加密
  • Token包括签名信息,可以由颁发系统或者其他系统进行验证,保证token的完整性

为此,首先我们需要决定和设计Token应当包括哪些信息, 具体如下: 这个Token是一个字符串的形式,大体包括三个方面的内容,使用分号分开:

[头信息];[内容信息];[签名信息]

一个实例如下: V00AAs1666h8cizn;E1tFib82Ap12tZJyoZIs7ZVivdPdLUEuliZAkulMiqbf5pD3DZECbLIEzZMB7dX5cNn1kxRIMoVMxisGRsgx8RwJmyZRzy3UI01N6U3CDZlP59zpbA==;19ka5c3

这种Token一般长度在100~300个字符(根据内容和签名方式有所差异,当然,在这三个字符串内部,不能使用分号,其中:

  • 头信息包括: token类型(一个字符),系统信息(4个字符),IP地址编码(若干字符),时间编码(可以固定为5个字符)
  • 内容信息,是一个Base64字符串,可以由信息的简单编码,或者一个加密后的结果
  • 签名信息,是对token前面字符串内容的私钥签名或者简单签名,签名方式和密钥由服务端确定,如果使用私钥签名,则其他系统可以对此token进行验证,否则只能由生成token的系统来验证

token类型,确定了这个token的生成方式,和基于此方式的token验证和处理方式(对应的算法),我们现在暂时设计了两种类型(可以随时扩展和兼容)

  • V: 全私有,私有签名,token内容加密,只有生成方才能对它进行验证和解密
  • P: 全公开,使用私钥签名,公钥验证,内容虽然是base64编码,但可以认为是公开的,只能保证这个token来源于颁发系统,当然在外部也可以进行时间和请求地址的有效性检查

系统信息是保留的,由业务要求确定(如表示请求系统或者服务系统等),IP编码和时间编码的实现也有相应的规则,详见实现代码。

实现和代码

Token的内容和格式确定后,我们针对编写了一个JS程序方法,来实现以上构想和设计要求。

要点如下:

  • 此方法支持两种模式,V(私有,载荷加密,私有验证),P(公共,载荷Base64,公钥验证)。
  • 时间为当前时间+过期时间, /120000的36字符编码
  • IP地址格式不限制(可能有v6的地址),统一摘要后再编码,验证时比较编码一致,当然也可以忽略检查
  • 加密,签名和验证等操作,这里使用的是NaCl库,详见笔者的另一篇文章《NaCl密码学程序库应用和研究》(juejin.cn/post/722637…)

Token应用,生成和验证

下面是基于上述实现,在具体应用中,生成和检查token的示例代码:


    // token params 
    let 
    key  = codec.from(VK_SIGN,"base64"), // sign key and hmac key
    pkey = codec.from(PK_SIGN,"base64"), // sign key and hmac key
    ip = "192.168.9.134",
    prefix = "00AA", // sub system id
    duo = 10; // *2min
    content = { i: "1056", r: 129, n: "颜建华", o: 5101} ;

    // genarate token 
    let token3 = mtoken({ ip, content, prefix, duo, key, tmode: "V" });
    console.log("Token-V:", token3.length, token3);

    // check token
    let check3= mtoken({ ip, content, prefix, duo, key, token: token3 });
    console.log("Token Check3:", check3);

生成token的方法,需要先根据业务需求和环境配置,构造一个参数对象,包括了:

  • IP(可能来自框架的req对象),
  • content,业务载荷(应该是JSON对象,如账号信息等),
  • prefix,业务前缀(四个字符,如0000),
  • duo,过期时间(乘以2分钟),
  • key,签名密钥和加解密密钥,
  • pkey,签名公钥(如果使用公钥签名的话)
  • token,如果是验证模式,需要输出token的base64字符串
  • tmode, 如是生成模式,需要选择生成何种类型的token,如V或者P

在生成模式下,函数的输出是一个token字符串。在业务中就可以用于http请求中的内容;在验证模式下,如果验证失败,则返回null,否则返回业务载荷信息(就是说验证的同时进行了解密或解码)。

在实际的HTTP应用当中,通常在客户端完成登录认证后,服务器根据业务需求和运行环境,构造一个Token,颁发给客户端;客户端在后续的请求过程中(通常是使用AJAX请求服务端的API接口),就可以附加此Token;服务端从请求信息中分离Token,验证通过后,可以使用Token中的用户信息,来检查用户操作权限,并进一步提供操作和数据服务。

当然,根据业务场景和需求的不同,我们可以考虑token的不同附加方式:

  • 放在HTTP请求头中,相对安全和规范,建议在前后端分离和服务器间通讯的场景使用,但开发实现稍微复杂
  • 放在请求参数中,适用性更好,比如在URL中,可以作为连接和重定向地址使用,开发和实现也比较简单

那么,具体而言,在这个过程中,安全性是如何保证的呢?

用户登录成功拿到Token之后,通常将Token保存在浏览器的Session Store中,通常也不进行任何处理,并且只在API请求时才使用,减少了信息暴露的风险; Token的内容可以是加密的,第三方甚至客户端都不知道其中的具体内容,只有服务器解密后知晓;Token是可以通过签名来验证的,外部既不能伪造内容,也不能伪造签名,保证了信息和内容的完整性;如果使用公钥,第三方也可以来验证Token的有效性; Token信息中包括了原始请求IP地址和有效时间,服务端可以验证这些信息的有效性,并拒绝失效的Token。 这些设计,都有力的保障了HTTP应用过程中的安全可控。

小结

本文探讨了HTTP会话的实现和保障机制,特别提出了一种比较安全的Token设计思路,并且编写了相关的代码来展示其的实现和使用,最后讨论了这些设计和实现的安全性。

转载自:https://juejin.cn/post/7226604941727563813
评论
请登录