javaee中session的验证_使用JWT实现sessionless验证(基于Node+Express+Passport JS)

22990734c6760d808c599337d72120df.png
作者:Bryan Manuele
原文:Sessionless Authentication using JWTs (with Node + Express + Passport JS)

——学习基于JWT的无用户会话(sessionless)验证的理论和最佳实践

使用有状态的用户session和储存在cookie中的session id进行验证的策略已经有几十年历史了。但随着面向服务架构和网络服务的出现,促进了使用sessionless原则设计应用的思想。

JWT提供了一种无状态的验证解决方案,不需要再在服务器跟踪session数据。相反,JWT允许我们安全的在客户端直接以JWT的形式储存session数据。

12eb6b84751a25d4c517c890b37a17be.png
JWT本质上是JSON形式的session数据负载,并由服务器签名。

JWT受到了很多批评和怀疑,但事实上session验证和JWT验证都有很多产品应用,这两种方式用于处理用户验证都是安全和稳健的。如果你认为在你的系统架构中实现无状态是很重要的实践,那么JWT就是为你准备的。在本文,我们将讨论JWT是什么,选择使用JWT所要做的权衡,以及如何在你的架构中安全的实现它们。

验证是如何工作的

在我们开始之前,我们需要确定验证的流程看起来是什么样的:

  1. 用户使用POST(通过HTTS)传给服务器验证的细节:{ username, password }
  2. 服务器确定该用户是否是用户自己声称的身份
  3. 如果用户的验证尝试成功通过,接下来服务器发送某种形式的数据(一般是token或者session id),在接下来的每次请求中可以附加上这种数据,这样可以识别用户是否经过验证。

使用sessionless验证,客户端接收到的数据负载是JWT。JWT应该包含编码过的用户标识,它是后端服务器签名过的JSON格式。我们把JWT放到cookie中,因此不必在local-storage中储存它以免受到XSS攻击。以下是名为TheLegend27的用户使用JWT验证的流程:

5608cc7443f0bddb8361f92422c98470.png

cookie是一种通过验证后随着每次请求一起发送的特殊头部。它还可以方便的跨过用户session实现持久化。这意味着TheLegend27登录成功后,他在之后的每一次请求中都将他的JWT一起发送。我们所要做的是验证他的身份,检查请求中的cookie并验证JWT。

需要注意的重要事项:

  • 我们不在服务器跟踪用户的session!这是JWT验证和session验证的明显不同之处。使用sessionless验证我们就少了一个需要担心的数据源。
  • 我们的验证流程超级简单!如果你只是想尽可能直观,快速的在Web应用中实现身份验证,那么JWT是不错的方案。

这就是使用JWT实现sessionless验证的原理。如果你已经熟练使用session验证,这一流程看起来有些熟悉。JWT看起来与储存在cookie中HS256加密的session_id非常相似。事实上,JWT默认就是使用HS256签名的。两者之间的区别是JWT在负载中编码了所有session数据,而session_id从session表中查询session。

JWT去除了在后端跟踪session的需求。而是将session数据编码在JWT负载中。这里需要做权衡的是JWT的大小和它的负载大小成正比。幸运的是,携带形如{user_id, expiration_date }的负载对大多数情况都是足够的了。

所以,理论就是这样。我们来开始实战!

6853a25a09edb9d3e04e191356c78f7d.png

在讨论具体实现之前,让我们先来讨论在验证过程中保护自己免疫最常见的攻击/漏洞的最佳实践。

  • XSS和SQL / noSQL注入攻击
  • 用户证书暴力破解攻击
  • 攻击者获取用户JWT/cookie
  • 攻击者获取数据库的副本或读取凭证

JWT和最佳实践可以保护我们免疫所有这些攻击。让我们来看怎么做到的:

XSS攻击

这种攻击是我们最容易预防的。一种试图保护我们免受XSS和代码注入的简单做法是清洁用户输入,如使用_.escape(userInput)。但这一方法的问题是,对于私有数据,盲目信任库函数能恰当清洁用户输入以免受sql xss 攻击是很天真的。输入清洁是是第一层重要的防御,只用它还不够。

一种保护用户敏感数据的更牢靠的方式是使用ORM/ODM,这会强制使用参数化查询。如果我们用的是SQL,也可以使用它的存储过程语句,这样查询过程是在数据库层定义的,而不是代码层。

如果你对XSS和查询清洁的最佳实践感兴趣,我强烈推荐一个由开放网络应用安全项目基金会提供的防御XSS注入攻击最佳实践的资料

暴力攻击用户证书

攻击者暴力破解用户证书有两种方法:

A)他们会攻击单个用户,尝试排列组合密码直到匹配为止。

B)他们会攻击多个用户,使用一个常用密码表进行浅暴力破解,直到匹配为止。这里有一个很好的例子:https://hackernoon.com/picking-the-low-hanging-passwords-b64684fe2c7

对于B类攻击我们能做的不多。如果用户把密码设为p@ssw0rd,那么我们除了强制使用更严格的密码策略,所能做的不多。

bdc6d420fa61df4c5c3c3af65e403638.png

事实证明我们可以很好的保护自己免受A类攻击。关键的地方在于让处理登录请求的时间需要花不少时间。如果需要

种排列组合数来破解一个8位密码,而每种排列需要花500ms的时间计算,那么攻击者就很难暴力破解用户证书了。可以使用一个名为Bcrypt的库,使得认证需要花不少时间,我们会在下面介绍。

JWT受损

这是我们能遇到的最坏可能的攻击,因为这最难解决。幸运的是,JWT很少受损,因为我们在cookie中储存JWT,并使用HTTPS来进行网络传输。由于我们从不在LocalStorage中储存JWT,只在cookie中储存,恶意的攻击者不能使用XSS偷走用户的JWT。

如果攻击者以某种未公开方法去偷用户的JWT,那么很不幸没有很多办法可以预防。为了减少破坏,你应该设计让你的应用要求在高级别档案传输之前执行重新验证,例如在购买和更改密码之前。你的JWT应该有过期日期。这样受损的JWT只能生效这么长时间。

151a9cc36a261394b6c58f80c5f2f5fa.png
这是在facebook.com上打开开发人员工具时显示的内容。这是防止self-xss的好方法

数据库受损

事实证明,即使黑客拿到了我们的数据库读取凭据,我们仍可以通过数据混淆处理保护用户数据。

如果我们有密码,我们不希望将其以纯文本形式存储在我们的数据库中。相反,我们可以对密码进行salt和hash,只存储salt和hash而不是我们的纯文本密码。

想象我们有一个密码p@ssw0rd。salt是一个随机的字符串,我们把它附在密码上('p@ssw0rd' + 'asdf253$n5'),然后将它传给hash算法:SHA256('p@ssw0rd' + 'asdf253$n5')。随后我们将salt和hash的结果储存在用户表里。

470251e5025fa2cbf0831cc39d06d820.png

如果我们的数据库受到损害,这也可以很好的保护我们的用户密码。你仍然需要采取行动,例如请求所有用户重置他们的密码,但这将为你带来足够的时间。

事实证明有一种标准的方法来salt和hash你的用户密码,这样就不需要在用户表中储存salt和hash字段。这一解决方案称为Bcrypt。

Bcrypt

Bcrypt是一种在1999年诞生的密码hash算法,它对salt和hash密码做了标准化。它也可以保护密码被暴力破解,因为它让验证过程需要高强度计算,慢到爆。我们将在我们的验证实现中使用该方法。

这就是经过Bcrypt处理后的密码的样子。它包含了hash算法的版本,hash成本,salt值,和hash后的密码值。

982f9b7639039fb4cf56fe86ed10f3be.png
Hash成本越高,验证需要的算力越多

现在我们已经拥有了所需的知识,让我们开始实现验证

我将使用MongoDB,你可以选择自己熟悉的数据库。下面是一个普通的mongoose User Schema

const 

接下来,我们将注册两个Passport策略。分别是Local Strategy和JWT Strategy。在后面,当我们调用passport.authenticate('local')或passport.authenticate('jwt')时,就会调用各自的中间件。

const 

Local strategy从req.body中提取username和password,并查询用户表以验证该用户。

JWT strategy从请求中的cookie中提取JWT,使用应用中的secret验证JWT的签名。

7b7248e2ba8f4db367663ff4744afd5c.png

最后,很重要的是,我们定义/login和/register路由

const 

/register 路由很直观,我们创建一个新的用户并将它存入用户表中,如果成功就返回状态码200。

/login路由更复杂一些。我们来分解一下:首先使用local strategy策略验证。如果验证成功,我们给JWT编写一个负载。随后调用req.login,这可以将负载放在req.user上。然后我们调用jwt.sign对JWT签名。最后在cookie中设置JWT。

现在我们来建一个需要验证的路由,可以使用JWT中间件来验证用户是否合法。

router

总结

就是这样!这里有整个兔子洞(注:爱丽丝梦游仙境中通向迷失世界)等待着那些对安全和认证感兴趣的人。我个人被困在这个兔子洞里的时间比我承认的要长得多。

本文是我对JWT最佳实践的研究成果,这应该足够让你尝试一下sessionless认证。JWT是一种优雅的认证方案,我希望本文阐明了它的工作原理,并让你有所启发,自己安全的实现它。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值