越来越多的开发者开始学习 JWT 技术并在实际项目中运用 JWT 来保护应用安全。一时间,JWT 技术风光无限,很多公司的应用程序也开始使用 JWT(Json Web Token)来管理用户会话信息。本文将从 JWT 的基本原理出发,分析在使用 JWT 构建基于 Token 的身份验证系统时需要谨慎对待的细节。
任何技术框架都有自身的局限性,不可能一劳永逸,JWT 也不例外。接下来,将从 JWT 的概念,基本原理和适用范围来剖析为什么说 JWT 不是银弹,需要谨慎处理。
众所周知,如果我们的账户信息(用户名和密码)泄露,存储在服务器上的隐私数据将受到毁灭性的打击,如果是管理员的账户信息泄露,系统还有被攻击的危险。那么,JWT 的信息发生泄露,会带来什么样的影响?该如何防范?这将是本文重点阐述的内容。
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)的原理。
JSON Web Token(JWT)是一个基于 RFC 7519 的开放数据标准,它定义了一种宽松且紧凑的数据组合方式,使用 JSON 对象在各应用之间传输加密信息。该 JSON 对象可以通过数字签名进行鉴签和校验,一般地,JWT 可以采用 HMAC 算法,RSA 或者 ECDSA 的公钥/私钥对数据进行签名操作。
一个 JWT 通常有 HEADER (头),PAYLOAD (有效载荷)和 SIGNATURE (签名)三个部分组成,三者之间使用“.”链接,格式如下:
下面是的字符串是一个JWT的实际案例:
注意三者之间有一个点号(“.”)相连
为了更直观的了解JWT的创建过程和使用方式,我们通过一个简单的例子来演示这两个过程。
JWT通常由“标头.有效载荷.签名”的格式组成。其中,标头用于存储有关如何计算JWT签名的信息,如对象类型,签名算法等。下面是JWT中Header部分的JSON对象实例:
在此 JSON 对象中,type 表示该对象为 JWT,alg 表示创建 JWT 时使用HMAC-SHA256 散列算法计算签名。有效载荷主要用于存储用户信息,如用户 ID,Email,角色和权限信息等。下面是有效载荷的一个简单示例:
而签名则需要使用 Base64URL 编码技术对标头 (Header 和有效载荷(Payload) 进行编码,并作为参数和秘钥一同传递给签名算法,生成最终的签名 (Signature)。以 HMAC-SHA256 算法为例,下面是生成签名的一个伪代码:
现在,我们已经了解了 JWT 的基本原理,接下来将使用 Java 来演示生成 JWT 的完整过程。
以 Maven 工程为例,需要在 pom.xml 文件中添加入下的配置信息:
如果是非 Maven 工程,你也可以到 Maven 中央仓库搜索 jjwt,然后选择相应的版本(0.9.0)下载到本地,并将 jar 包添加到工程的类路径(classpath)中。
在工程中新建 JJWTUitls.java 工具类,使用 jjwt 提供的方法实现 JWT 的生成,实现细节如下:
在此方法中,JJWT 已经处理好 JWT 标头(Header)的信息,我们只需要提供签名所使用的算法(如 SignatureAlgorithm.HS256),有效载荷,主题(包含了用户信息),过期时间(exp-time)和秘钥即可,最后使用 jjwt 的 builder()方法组装 JWT。下面是生成秘钥方法 key() 的源代码:
使用 JJWT 解析 JWT 相对简单,首先获取秘钥,然后通过 Jwts.parse() 方法设置秘钥并 JWT 进行解析,实现细节如下:
最后,在工程中新建一个 JavaJWT.java 类,并在 main 方法中检验 JJWTUtils 工具类中生成和解析 JWT 两个方法是否有效。实现细节如下:
如上图所示,“jwt”将作为 JWT 标头(Header) “type” 的值,有效载荷(payload)中的主题信息如下:
且 JWT 签名的有效时间为60,000毫秒。执行 main 方法,输出信息如下所示:
从测试结果可以看出,我们成功的使用 JJWT 创建并解析了 JWT。接下来,我们将了解到在实际的应用中,JWT 对用户信息进行验证的基本流程。
在身份验证中,当用户成功登录系统时,授权服务器将会把 JSON Web Token 返回给客户端,用户需要将此凭证信息存储在本地(cookie或浏览器缓存)。当用户发起新的请求时,需要在请求头中附带此凭证信息,当服务器接收到用户请求时,会先检查请求头中有无凭证,是否过期,是否有效。如果凭证有效,将放行请求;若凭证非法或者过期,服务器将回跳到认证中心,重新对用户身份进行验证,直至用户身份验证成功。以访问 API 资源为例,下图显示了获取并使用 JWT 的基本流程:
现在,我们已经完全了解了 JWT 是什么,怎么实现以及用来干什么这三个问题。在上述的案例中,我们使用 HS256 算法对 JWT 进行签名,在这个过程中,只有身份验证服务器和应用服务器知道秘钥是什么。如果身份验证服务器和应用服务器完全独立,则应用服务器的 JWT 校验工作也可以交由认证服务器完成。当客户端对应用服务器发起调用时,应用服务器会使用秘钥对签名进行校验,如果签名有效且未过期,则允许客户端的请求,反之则拒绝请求。
优势与劣势是相对而言的,这里主要以传统的 Session 模式作为参考,总结使用 JWT 可以获得优势以及带来的弊端。
使用 JSON Web Token 保护应用安全,你至少可以获得以下几个优势:
更少的数据库连接:因其基于算法来实现身份认证,在使用 JWT 时查询数据的次数更少(更少的数据连接不等于不连接数据库),可以获得更快的系统响应时间。构建更简单:如果你的应用程序本身是无状态的,那么选择 JWT 可以加快系统构建过程。
跨服务调用:你可以构建一个认证中心来处理用户身份认证和发放签名的工作,其他应用服务在后续的用户请求中不需要(理论上)在询问认证中心,可使用自有的公钥对用户签名进行验证。
无状态:你不需要向传统的 Web 应用那样将用户状态保存于 Session 中。
JWT 不是万能的,使用 JWT 也会带来诸多问题。就个人使用情况,使用 JWT 时可能会面临以下几个麻烦:
考虑这样一个问题:如果客户端的 JWT 令牌泄露或者被盗取,会发生什么严重的后果?有什么补救措施?
不管是基于 Sessions 还是基于 JSON Web Token,一旦密令被盗取,都是一件棘手的事情。接下来,将讲述基于 JSON Web Token 的方式发生令牌泄露是该采取什么样的措施(解决方案包含但不局限与本文所涉及的内容)。
为了防止用户 JWT 令牌泄露而威胁系统安全,你可以在以下几个方面完善系统功能:
本文从 Token 的基本含义,JSON Web Token 的原理和流程出发,并结合实际的案例分析了使用 JSON Web Token 的优势与劣势;与此同时,结合自己实际使用 JSON Web Token 过程中发现的问题给出了避免“踩坑”的解决方案。
世上没有完美的解决方案,系统的安全性需要开发者积极主动地去提升,其过程是漫长且复杂的,也许一开始的 MVP 系统并不需要那么强大的安全性,但随着业务的增长系统需要升级,或者说最终将重写整个系统,提前了解技术背后可能会遇到的问题,不失为一种好的编程习惯。
JSON Web Token 的出现,为解决 Web 应用安全性问题提供了一种新思路。但JSON Web Token 也不是银弹,你任然需要做很多复杂的工作才能提升系统的安全性。