通用安全的token化解决方案
一、协议过程
a. 我们先来看token化的协议过程
图 一.a
在用户输入正确的用户名和密码后,"图 一.a" 中 Step "2." 将tokens放入到cookie中, 拿点融举例子,我们会将cookie放到 ".dianrong.com”这个域名下。看起来非常简单,认证、单点登陆全都有了。
b. 我们再来看一看CAS的SSO协议过程
图 一.b
图 一.b 描述了开源应用CAS定义的单点登陆协议
有重定向、有指定service, 临时ticket传递、有后台有状态ticket的验证,整个流程步骤比较多。倘若我们的应用是单页的前后端分离应用,前端和后端仅通过ajax进行访问,在重定向这一步会卡住。
点融的应用几乎都是前后端分离,我们针对这种情况做了一些定制(本文重点不是这个, 有兴趣可以找作者)。
通过这个协议过程的对比,我们发现,token无疑提供了更高的便捷性。
二. Token化的优点和缺点
优点:
• 无状态、性能 —— token机制在服务器端不需要存储session信息,因为token自身可包含登陆用户的信息,例如 手机号、用户id,在客户端cookie存储这个信息从性能上来讲, 节省一次网络,仅做一次token的验签,将io消耗转换为cpu消耗.
• 解耦 —— token在内网生成,可由任意一个集成了SDK的服务器端 设置到客户端,它的生成 和 验证方案可随意切换,中心服务器用于维护状态
• 适用于现代Clients —— 比如微信小程序、比如原生平台
• 便捷 —— 集成方便、测试方便、客户端透明
• 标准 —— 比如RFC7519,使用JWT格式,可以跨平台
ps: token化之后,token理论上可以交给前端,由前端自己决定保存方式、传递方式,也因此可以实现跨域变得更加方便方式。 但由此而引出的安全问题后端无法掌控,因而我不认为跨域访问是一个优势, 跨域的授权, 应该由oauth2.0协议来完成。
缺点:
• Security —— 便捷 和 安全是一对好伙伴,在获得便捷的同时应当考虑安全性是否下降
• Revoke token —— token无状态,属于Bearer Token 因此无法对已经签发的token进行撤销, 只能等待其失效.
• Renew token —— token无状态,所以不会像session随着每一次的访问而修改session失效时间.
三、平衡缺点,获得优点
A. Security
为了尽可能的限制安全问题发生的范围,我们生成的所有token都会放入cookie并且cookie.setMaxAge(-1), 常见的security问题以及其解决方案:
1. Man-in-the-Middle. 使用https加密,避免中途有人监听窃取cookie, 以java代码为例, cookie new出来之后需要 setSecure=true, 只在https的情况下设置cookie.
2. Cross-Site Scripting (XSS), cookie new出来之后需要 setHttpOnly=true, 这样js代码就无法操作cookie, 自然XSS无处遁寻。针对服务器端 和 非WEB客户端,则需要分别保证自己依赖的library不包含可执行代码,客户端保证服务器返回的任何内容都有执行类似escapeHtml的操作.
3. Cross-Site Request Forgery (CSRF), 在访问某些比较敏感接口时(比如转账),使用Double-Submit Cookie方案解决。 这个安全问题有一点需要注意:不要滥用CORS(cross origin resource share), 这样会导致 CSRF方式的漏洞, 完全暴露给恶意攻击者。
B. Renew & Revoke token
想要克服无状态的这两个缺点,首先看 两个概念 : Access & refresh token签名tokens
• Access token拥有比较短的生存时间, 可以被认作为一个无状态的可信任的字符串。
• Refresh token拥有比较长的生存时间,是用来换取access token的。refresh token应该可以被撤销(Database + cache).
基于这两个从oauth2借来的概念,我们的方案如图:
图 三.a server-side use case
图 三.b client-side 流程图
如图三.b, 我们将refresh token有状态化,通过对refresh token的控制,从而达成对token renew&revoke的目的。
通过Access Token & Refresh token pair 的有效期的配置,我们即拥有了token无状态的优势(由access token提供),也拥有了控制token状态的能力。
以下举一些常见的token可配置的例子:
• 敏感应用(如 银行app)
Access token TTL(Time to live) : 1 minute
Refresh token TTL : 30 minutes
• 普通应用
Access token TTL: 5 minutes
Refresh token TTL : 2 hours
• 特殊身份不敏感应用(如今日头条、抖音)
Access token TTL = 1 hour
Refresh token TTL = 2 years
Balance是程序员世界很有趣的话题。
四、签名 & 验签 算法
RSA or HMAC or others?
主要从两个维度考虑问题:
1. 安全
2. 性能
RSA 算法的安全基于大数因式分解,而大数因式分解无特效公式,因此RSA只要密钥/公钥对足够大,就足够安全。我在我本机做了一些性能测试, 将签名内容SHA256之后用RSA算法签名验签时间如下:
* 4096位RSA私钥公钥对 签名 --> 每个49.36ms, 验签 --> 每个 0.6667ms
* 2048位RSA私钥公钥对 签名 --> 每个6.43ms, 验签 --> 每个 0.1844ms
* 1024位RSA私钥公钥对 签名 --> 每个1.088ms, 验签 --> 每个 0.0548ms
RSA 私钥和公钥不一样,在token做authentication的case中,签名使用私钥,验签使用公钥。
在内网,可以仅将公钥交给非签发签名的系统来验签(防止其他系统泄露私钥), 而私钥仅由签发签名的系统保管。
HMAC 只有密钥,签名验签很快, 客户端知道密钥,易泄露。
不论用哪种算法,都不需要将公钥或私钥交给前端。
五、总结
• Token放进cookie, Cookie 应该 setSecure = true , httpOnly, .domain.com, setMaxAge(-1)
• Access Token + Refresh Token的方式 是一个非常好的scaling 策略
• 每一次使用refresh token(如 获取新access token、refreshtoken) 都需要访问服务器询问其状态
六.其他风险
• 签名密钥泄露
• 密钥被破解
签名密钥泄露之后,理论上得到密钥的人可制造任意被服务器相信的内容。需要系统拥有随时更换密钥的能力、周期性的更换密钥。
七、Reference Links:
• 流程图地址:
https://www.processon.com/view/link/5ae9249de4b09b1bf6369cba
• 滴滴passport经验:
http://www.hello-code.com/blog/architecture/201607/6099.html
• 讲真别再用JWT了!
https://www.jianshu.com/p/af8360b83a9f
• OWASP:
https://www.owasp.org/index.php/JSON_Web_Token_(JWT)_Cheat_Sheet_for_Java#Token_weak_secret
• Building Secure User interfaces With JWTs(JSON Web Tokens) :
https://www.slideshare.net/stormpath/building-secure-user-interfaces-with-jwts
congtou