ramostear.comramostear.com 谭朝红的技术分享博客

格言 编程是一门技术,也是一门艺术 !

JSON Web Token绝非银弹,“蹲坑”需谨慎小心!

JSON Web Token绝非银弹,“蹲坑”需谨慎小心!

越来越多的开发者开始学习JWT技术并在实际项目中运用JWT来保护应用安全。一时间,JWT技术风光无限,很多公司的应用程序也开始使用JWT(Json Web Token)来管理用户会话信息。本文将从JWT的基本原理出发,分析在使用JWT构建基于Token的身份验证系统时需要谨慎对待的细节。

​ 任何技术框架都有自身的局限性,不可能一劳永逸,JWT也不例外。接下来,将从JWT的概念,基本原理和适用范围来剖析为什么说JWT不是银弹,需要谨慎处理。

​ 众所周知,如果我们的账户信息(用户名和密码)泄露,存储在服务器上的隐私数据将受到毁灭性的打击,如果是管理员的账户信息泄露,系统还有被攻击的危险。那么,JWT的信息发生泄露,会带来什么样的影响?该如何防范?这将是本文重点阐述的内容。

1、什么是Token?

​ Token(令牌)通常是指Security Token(安全令牌),可以分为Hardware Token(硬件令牌),Authentication Token(授权令牌),USB Token(USB令牌),Cryptographic Token(加密令牌),Virtual Token(虚拟令牌)和Key Fob(钥匙卡)。其主要作用是验证身份的合法性,以允许计算机系统的用户可以操作系统资源。生活中常见的令牌如:登录密码,指纹,声纹,门禁卡,银行电子卡等。Token的主要目的是为计算机系统提供一个可以识别用户的任意数值,例如“token123”这样的明文字符串,或者像“41ea873f-3a4d-57c8-1e38-ef74f31015af”之类的加密字符。

​ 由于篇幅关系,Token就了解到这里。接下来将聊聊有关JWT(JSON Web Token)的原理。

2、什么是JSON Web Token?

​ JSON Web Token(JWT)是一个基于RFC 7519的开放数据标准,它定义了一种宽松且紧凑的数据组合方式,使用JSON对象在各应用之间传输加密信息。该JSON对象可以通过数字签名进行鉴签和校验,一般地,JWT可以采用HMAC算法,RSA或者ECDSA的公钥/私钥对数据进行签名操作。

​ 一个JWT通常有HEADER(头),PAYLOAD(有效载荷)和SIGNATURE(签名)三个部分组成,三者之间使用“.”链接,格式如下:

下面是的字符串是一个JWT的实际案例:

注意三者之间有一个点号(“.”)相连

​ 为了更直观的了解JWT的创建过程和使用方式,我们通过一个简单的例子来演示这两个过程。

3、如何创建JWT?

​ JWT通常由“标头.有效载荷.签名”的格式组成。其中,标头用于存储有关如何计算JWT签名的信息,如对象类型,签名算法等。下面是JWT中Header部分的JSON对象实例:

在此JSON对象中,type表示该对象为JWT,alg表示创建JWT时使用HMAC-SHA256散列算法计算签名。有效载荷主要用于存储用户信息,如用户ID,Email,角色和权限信息等。下面是有效载荷的一个简单示例:

而签名则需要使用Base64URL编码技术对标头(Header)和有效载荷(Payload)进行编码,并作为参数和秘钥一同传递给签名算法,生成最终的签名(Signature)。以HMAC-SHA256算法为例,下面是生成签名的一个伪代码:

​ 现在,我们已经了解了JWT的基本原理,接下来将使用Java来演示生成JWT的完整过程。

4、基于Java实现的JWT(JJWT)案例

4-1、依赖

以Maven工程为例,需要在pom.xml文件中添加入下的配置信息:

如果是非Maven工程,你也可以到Maven中央仓库搜索jjwt,然后选择相应的版本(0.9.0)下载到本地,并将jar包添加到工程的类路径(classpath)中。

4-2、生成JWT

​ 在工程中新建JJWTUitls.java工具类,使用jjwt提供的方法实现JWT的生成,实现细节如下:

在此方法中,JJWT已经处理好JWT标头(Header)的信息,我们只需要提供签名所使用的算法(如SignatureAlgorithm.HS256),有效载荷,主题(包含了用户信息),过期时间(exp-time)和秘钥即可,最后使用jjwt的builder()方法组装JWT。下面是生成秘钥方法key()的源代码:

4-3、解析JWT

​ 使用JJWT解析JWT相对简单,首先获取秘钥,然后通过Jwts.parse()方法设置秘钥并JWT进行解析,实现细节如下:

4-4、测试JJWT

​ 最后,在工程中新建一个JavaJWT.java类,并在main方法中检验JJWTUtils工具类中生成和解析JWT两个方法是否有效。实现细节如下:

如上图所示,“jwt”将作为JWT标头(Header)“type”的值,有效载荷(payload)中的主题信息如下:

且JWT签名的有效时间为60,000毫秒。执行main方法,输出信息如下所示:

​ 从测试结果可以看出,我们成功的使用JJWT创建并解析了JWT。接下来,我们将了解到在实际的应用中,JWT对用户信息进行验证的基本流程。

5、 JSON Web Token的工作流程

​ 在身份验证中,当用户成功登录系统时,授权服务器将会把JSON Web Token返回给客户端,用户需要将此凭证信息存储在本地(cookie或浏览器缓存)。当用户发起新的请求时,需要在请求头中附带此凭证信息,当服务器接收到用户请求时,会先检查请求头中有无凭证,是否过期,是否有效。如果凭证有效,将放行请求;若凭证非法或者过期,服务器将回跳到认证中心,重新对用户身份进行验证,直至用户身份验证成功。以访问API资源为例,下图显示了获取并使用JWT的基本流程:

​ 现在,我们已经完全了解了JWT是什么,怎么实现以及用来干什么这三个问题。在上述的案例中,我们使用HS256算法对JWT进行签名,在这个过程中,只有身份验证服务器和应用服务器知道秘钥是什么。如果身份验证服务器和应用服务器完全独立,则应用服务器的JWT校验工作也可以交由认证服务器完成。当客户端对应用服务器发起调用时,应用服务器会使用秘钥对签名进行校验,如果签名有效且未过期,则允许客户端的请求,反之则拒绝请求。

6、使用JSON Web Token的利弊

​ 优势与劣势是相对而言的,这里主要以传统的Session模式作为参考,总结使用JWT可以获得优势以及带来的弊端。

6-1、 使用JWT的优势

​ 使用JSON Web Token保护应用安全,你至少可以获得以下几个优势:

  1. 更少的数据库连接:因其基于算法来实现身份认证,在使用JWT时查询数据的次数更少(更少的数据连接不等于不连接数据库),可以获得更快的系统响应时间。
  2. 构建更简单:如果你的应用程序本身是无状态的,那么选择JWT可以加快系统构建过程。
  3. 跨服务调用:你可以构建一个认证中心来处理用户身份认证和发放签名的工作,其他应用服务在后续的用户请求中不需要(理论上)在询问认证中心,可使用自有的公钥对用户签名进行验证。
  4. 无状态:你不需要向传统的Web应用那样将用户状态保存于Session中。

6-2、使用JWT的弊端

​ JWT不是万能的,使用JWT也会带来诸多问题。就个人使用情况,使用JWT时可能会面临以下几个麻烦:

  1. 严重依赖于秘钥:JWT的生成与解析过程都需要依赖于秘钥(Secret),且都以硬编码的方式存在于系统中(也有放在外部配置文件中的)。如果秘钥不小心泄露,系统的安全性将收到威胁。
  2. 服务端无法管理客户端的信息:如果用户身份发生异常(信息泄露,或者被攻击),服务端很难向操作Session那样主动将异常用户进行隔离。
  3. 服务端无法主动推送消息:服务端由于是无状态的,他将无法使用像Session那样的方式推送消息到客户端,例如过期时间将至,服务端无法主动为用户续约,需要客户端向服务端发起续约请求。
  4. 冗余的数据开销:一个JWT签名的大小要远比一个Session ID长很多,如果你对有效载荷(payload)中的数据不做有效控制,其长度会成几何倍数增长,且在每一次请求时都需要负担额外的网络开销。

​ JSON Web Token 很流行,但是它相比于Session,OIDC(OpenId Connect)等技术还比较新,支持JSON Web Token的库还比较少,而且JWT也并非比传统Session更安全,他们都没有解决CSRF和XSS的问题。因此,在决定使用JWT前,你需要仔细考虑其利弊。

7、JSON Web Token并非银弹,“蹲坑”需谨慎

考虑这样一个问题:如果客户端的JWT令牌泄露或者被盗取,会发生什么严重的后果?有什么补救措施?

​ 如果单纯的依靠JSON Web Token解决用户认证的所有问题,那么系统的安全性将是脆弱的。由于JWT令牌存储于客户端中,一旦客户端存储的令牌发生泄露事件或者被攻击,攻击者就可以轻而易举的伪造用户身份去修改/删除系统资源,岁如按JWT自带过期时间,但在过期之前,攻击者可以肆无忌惮的操作系统数据。通过算法来校验用户身份合法性是JWT的优势,同时也是最大的弊端——它太过于依赖算法。

​ 反观传统的用户认证措施,通常会包含多种组合,如手机验证码,人脸识别,语音识别,指纹锁等。用户名和密码只做用户身份识别使用,当用户名和密码泄露后,在遇到敏感操作时(如新增,修改,删除,下载,上传),都会采用另外的方式对用户的合法性进行验证(发送验证码,邮箱验证码,指纹信息等)以确保数据安全。

​ 与传统的身份验证方式相比,JWT过多的依赖于算法,缺乏灵活性,而且服务端往往是被动执行用户身份验证操作,无法及时对异常用户进行隔离。那是否有补救措施呢?答案是坑定的。接下来,将介绍在发生令牌泄露事件后,如何保证系统的安全。

8、使用JSON Web Token 爬坑指南

​ 不管是基于Sessions还是基于JSON Web Token,一旦密令被盗取,都是一件棘手的事情。接下来,将讲述基于JSON Web Token的方式发生令牌泄露是该采取什么样的措施(解决方案包含但不局限与本文所涉及的内容)。

为了防止用户JWT令牌泄露而威胁系统安全,你可以在以下几个方面完善系统功能:

  1. 清除已泄露的令牌:此方案最直接,也容易实现,你需将JWT令牌在服务端也存储一份,若发现有异常的令牌存在,则从服务端令牌列表中将此异常令牌清除。当用户发起请求时,强制用户重新进行身份验证,直至验证成功。对于服务端的令牌存储,可以借助Redis等缓存服务器进行管理,也可以使用Ehcache将令牌信息存储在内存中。
  2. 敏感操作保护:在涉及到诸如新增,修改,删除,上传,下载等敏感性操作时,定期(30分钟,15分钟甚至更短)检查用户身份,如手机验证码,扫描二维码等手段,确认操作者是用户本人。如果身份验证不通过,则终止请求,并要求重新验证用户身份信息。
  3. 地域检查:通常用户会在一个相对固定的地理范围内访问应用程序,可以将地理位置信息作为一个辅助来甄别用户的JWT令牌是否存在问题。如果发现用户A由经常所在的地区1变到了相对较远的地区2,或者频繁在多个地区间切换,不管用户有没有可能在短时间内在多个地域活动(一般不可能),都应当终止当前请求,强制用户重新进行验证身份,颁发新的JWT令牌,并提醒(或要求)用户重置密码。
  4. 监控请求频率:如果JWT密令被盗取,攻击者或通过某些工具伪造用户身份,高频次的对系统发送请求,以套取用户数据。针对这种情况,可以监控用户在单位时间内的请求次数,当单位时间内的请求次数超出预定阈值值,则判定该用户密令是有问题的。例如1秒内连续超过5次请求,则视为用户身份非法,服务端终止请求并强制将该用户的JWT密令清除,然后回跳到认证中心对用户身份进行验证。
  5. 客户端环境检查:对于一些移动端应用来说,可以将用户信息与设备(手机,平板)的机器码进行绑定,并存储于服务端中,当客户端发起请求时,可以先校验客户端的机器码与服务端的是否匹配,如果不匹配,则视为非法请求,并终止用户的后续请求。

总结

​ 本文从Token的基本含义,JSON Web Token的原理和流程出发,并结合实际的案例分析了使用JSON Web Token的优势与劣势;与此同时,结合自己实际使用JSON Web Token过程中发现的问题给出了避免“踩坑”的解决方案。

​ 世上没有完美的解决方案,系统的安全性需要开发者积极主动地去提升,其过程是漫长且复杂的,也许一开始的MVP系统并不需要那么强大的安全性,但随着业务的增长系统需要升级,或者说最终将重写整个系统,提前了解技术背后可能会遇到的问题,不失为一种好的编程习惯。

​ JSON Web Token的出现,为解决Web应用安全性问题提供了一种新思路。但JSON Web Token也不是银弹,你任然需要做很多复杂的工作才能提升系统的安全性。

宠物与牛-全栈工程师的神话与谎言

宠物与牛-全栈工程师的神话与谎言

宠物与牛-全栈工程师的神话与谎言

最近几年,在软件开发(尤其是Web开发)领域,“全栈工程师”成为了开发者津津乐道的热词。你或许会听到这样一些话语:“我正在学习全站开发”,“我是全栈工程师”,”我们正在招聘全栈工程师“。那么,”全栈“到底意味着什么?出自什么地方?具体含义是什么?带着这样三个问题,我们将一步步去揭露全栈工程师的神话与谎言。

​ 也许你不止一次的看到过且认为下面的这个等式是正确的,而且高大上:

如果这个等式在软件开发中是一个真命题,那么我们可以得出这样一个推导式:

那么,如果一个软件开发人员不会前端或者后端,亦或是不会基础架构开发,那么他就不是软件工程师。显然,这样的推论是不正确的,也就是说上述关于全栈工程师的定义在软件开发中是荒谬的,是一个被精心包装过的神话。

”full stack“(全栈)一词只是一个商业上的流行语,而不是指代具体的工程或者某一种技术。真正的软件开发者不会用”全栈“一词来描述自己的技术,”全栈开发“是一个没有任何意义的定义,它就好比”五彩斑斓的黑“一样荒谬。也许使用这一词语的开发者是想在应聘时取悦面试官,留下比较好的印象,因为谁都不想在面试上表现平平,对于招聘者而言,当他们提及”全栈“一词时,更多的是想寻找一个技术领袖,而非真正的”全栈“开发。

​ 那么“全栈开发”这个技术神话,是如何被成功塑造出来的呢?

​ “全栈开发”杜撰于商业解决方案中对技术问题的描述,全栈是指一个包含完整技术解决方案的全部技术框架。以Web开发为例,全栈就意味着从前端(用户交互界面)到后端(业务系统,数据库,操作系统等)的全套技术套件。而这种端到端(客户端-服务器端)的解决方案需要依赖于诸多技术,如TCP/IP协议,硬件设施,防火墙,路由,负载均衡,网络代理,Web容器,数据存储,操作系统等。这里的全栈,更为明确的是指代技术的完整性,即全栈开发等同于完整的技术解决方案:

从表面上看,全栈开发工程师很合乎逻辑,它也成了技术大咖一种能力的象征。前面说过,全栈开发意味着完整的技术解决方案,如果全栈开发工程师这一命题成立,那么相当于一名全栈工程师就代表这一个完整的技术解决方案,很明显,这是一个伪命题。即便是一个天才或者通才,他也不能说自己所掌握的开发技术代表着一个完整的解决方案,这就是为什么说全栈工程师只是一个神话的原因。

​ 接下来,让我们看看一些生活中常见的例子,以揭示全栈开发工程师中所隐藏的谎言。

​ 对于开发者,当被问及“你是全栈开发人员吗?”,其实他正真的意思是想知道你是否会使用数据库,操作系统,使用一种或更多的编程语言编写业务代码,以及是否会操作HTML页面。如果你是一名软件工程师,你给出的答案是肯定的,但这并不意味着你是一个全栈开发人员,因为全栈开发人员这个术语本身没有任何意义。实际的情况是,任何技术都对于其专门的岗位,例如UI工程师,Java工程师,PHP工程师,运维工程师,DBA,网络工程师,系统架构师等等,他们都有着自身专注的领域,如果按照全栈工程师的定义,一个UI工程师不具备 系统架构的能力,那么是否就可以说他不是一名软件工程师呢?这是一种错误的认识。我们不能将描述技术解决方案的术语转嫁到具体的开发岗位上,因为人不能代表一个完整的技术解决方案。

​ 对于企业,当其想要招聘一个“全栈工程师”时,就像在找“五彩斑斓的黑”一样没有意义,因为它根本就不存在。只有不同色度的黑色,而不存在如彩虹般绚烂的黑色。但奇怪的是,当涉及此问题是,面试者往往会列举很多案例来证明他是全栈开发人员,这是因为他要取悦面试官,没有任何人会说自己是“半吊子”工程师,更不会说自己不是软件工程师,所以才导致一个谎言只能换来另外一个谎言的结局。当你有招聘“全栈工程师”的想法时,不妨将其更换为“优秀的软件工程师”,“高级Java工程师”,“经验丰富的UI工程师”或者“五年以上工作经验的软件工程师”这一类术语更为有效。明确需求,才能找到合适的人选,细化问题,才能得到真实的答案。

​ 如果公司正处于初创期或者只有一两名开发人员,在资源(人力,财力)相对缺乏的情况下,“全栈开发人员”确实很诱人,也很适合你。因为在这种情况下你别无选择,你只能相信“全栈开发人员”能够为你提供一套完整的技术解决方案,并成功实施该它。但也不能因为开发人员数量少而将人和方案等同起来,在这种情况下,你所说的“全栈开发人员”应该是指公司的技术领袖,他们对软件开发有着强大的知识储备,同时具备较好的编程能力,能在创业初期为你提供最小化的,可用的技术解决方案以及MVP(Minimum Viable Product,即最小化可用产品)。当你的公司步入成长期时,需要摒弃寻找“全栈工程师”这种无意义的想法。因为在此时期,需要完善最小化的技术解决方案,将解决方案进行拆分,投入更多的人力(开发人员)到各子方案中,才有可能使一个完整的技术解决方案得以顺利实施。这是一个技术组件的综合体,并不是单靠个人能完成的任务。在实际的开发工作中,为了解决方案种涉及的技术问题,公司工作岗位上会出现如系统架构师,项目经理,数据分析师,UI/UX工程师,DBA,Java工程师,IOS工程师,Android工程师,测试工程师等一系列配套的角色,各角色之间分工明确,各司其职。一个人无论其能力再强,也无法在同一时间内,完成多个角色的多项工作,除非公司能排除时间因素,在软件开发的总体拥有成本中(TCO)将时间范围(Time Scope)剔除。任何以盈利为最终目的的商业公司都不会允许这种愚蠢的事情发生。因此,“全栈工程师”对于企业而言,也是一个神话臆想和天大的谎言。

​ 下面,我们再从技能组成上来分析为什么说全栈开发者是一个谎言。以Java Web开发为例,如果“全栈开发人员”不是一个神话,那么他需要在有限的时间内,高质量的完成下表所列举的工作。表中列举的内容并非完整内容,只是列举了一些相对重要的知识点。在这其中,一些技能可能只需数个小时即可掌握,而有一些技能需要数月甚至数年才能融会贯通。人们对于“全栈开发人员”的期望远远超出了普通人的适应能力,甚至有些“丧心病狂”。

​ 如果你是一名“全栈开发人员”,那么就意味着需要对每一种技术组件都有足够的理解,以便在方案实施的过程中快速作出合理的决策,并能够利用该项技术完成系统的开发。与此同时,还需要有较强的沟通能力,能够以简单明了的语言向上层决策者阐明使用该技术的必要性和合理性。但众所周知的是,各种技术组件的迭代更新周期远比我们掌握的速度快,每隔几个月,或者几年,就会有新的技术组件发布,作为“全栈开发人员”就需要在解决方案,方案实施,学习新组件这三者之间疲于奔命,如果有一种或几种技术组件不能及时掌握,抑或是无法掌握,那是否可以做出这样的判断:你将不再是一名软件工程师呢?显然,这是一种错误的,不合理的定义。

​ 既然说“全栈开发人员”是一个神话,那现在该如何正确的理解这个词呢?

​ 就我的理解而言,如果被问及“是否是全栈开发人员”,正确的回答是:“我是一名优秀的软件工程师”。如果对方不理解其中的含义,那么他可能是一个对软件工程缺乏认知的人,对软件开发的整套流程知之甚少,对于样的人,可以忽视其问题的存在。上述的回答有两层的含义,第一是具有完整的软件工程专业知识,这些知识体系能帮助开发者在提出解决方案时合理的选择相应的技术组件,其次是具备较强的学习能力和编码能力,能够在有限的时间范围内专注于完成一件事情并协同其他人员完成开发任务。因此,应该将“全栈开发”一分为二,分为“全栈”和“开发”,前者表示完整的计算机理论知识,后者则表示丰富的编码经验。此时再将二者结合则有一个合理的定义:利用完整的计算机知识,完成产品的研发工作。

​ 企业需要技术领袖而非技术神话,但不是每一个开发人员都是技术领袖。如果将企业比作一个“养殖场”,那么开发人员就好比被“饲养”的“动物”,技术大咖或者技术领袖好比是牛,牛体型大,输出的产品值高,但其“饲养”周期长,成本高,在短时间内很难有明显的产出,且适应市场需求变化的能力较差;作为“养殖场”的“饲养员”,不能一味的只“养殖”牛,还应根据消费市场的变化,养殖鸡,鸭,鹅,兔等宠物(小体型)级别的“经济动物”。这些宠物的特点是“生长”周期短,“饲养”成本低,且在有限时间范围内能够灵活的适应市场需求,单位时间内输出的经济产值远比牛的高。软件开发者需要完整的知识储备而不是自欺欺人的技术谎言,完整的,成体系的计算机知识,以及精益求精的专注精神,才能让自己称为一名优秀的软件工程师,架构师或者技术专家。

​ 如果你现在是一名软件开发的求职者,是时候对“全栈开发人员”这种神话坚决说不,粉碎“全栈工程师”的谎言,勇于的对自己的技能做出明确定义,如Web工程师,Android工程师,IOS工程师或者系统架构师;如果你是一名面试主官,请不要再使用“全栈工程师”,“全栈开发人员”这类毫无意义的词汇,可以更换为更具体的”UI工程师”,“系统架构师”或“技术专家”,无需要使用“我们的技术人员都是全栈开发者”这类谎言来安慰决策者和员工,请将“全栈”一词用于描述完整的技术解决方案中。软件开发作为一种讲究科学性,严谨性,系统性的工作,不需要神话和谎言来粉饰,脚踏实地才是王道。作为一名优秀的软件工程师(软件开发人员),需要使自身的计算机知识体系化,完整化,专业化,专注于某一领域而博览众长,才是对“全栈”最好的诠释。对于企业,需要“核常”兼备,全面发展,维系好宠物与牛之间的平衡关系,才能为用户提供正真意义上的全栈解决方案。