前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Apache NiFi中的JWT身份验证

Apache NiFi中的JWT身份验证

作者头像
@阿诚
发布2022-03-04 16:32:11
4K0
发布2022-03-04 16:32:11
举报
文章被收录于专栏:Panda诚

大部分文章译自原文:https://exceptionfactory.com/posts/2021/10/23/improving-jwt-authentication-in-apache-nifi/ 同时结合译文,参照NIFI(1.15)源码进行分析讲述举例说明

本文目的

  1. 深入对Apache NiFi的新版JWT身份验证深入理解。
  2. 为自定义外部应用程序访问使用了JWT身份验证的NIFI服务提供参考和开发依据。

背景知识

JSON Web Tokens为众多Web应用程序和框架提供了灵活的身份验证和授权标准。RFC 7519概述了JWT的基本要素,枚举了符合公共声明属性的所需编码,格式和已注册的声明属性名称(payload里属性称为声明)。RFC 7515中的JSON Web签名和RFC 7518中的JSON Web算法描述了JWT的支持标准,其他的比如OAuth 2.0框架的安全标准构建在这些支持标准上,就可以在各种服务中启用授权。

用于生成和验证JSON Web Tokens的库可用于所有主流的编程语言,这使得它成为许多平台上(身份验证)的流行方法。由于它的灵活性和几个库中的实现问题,一些人批评了JWT的应用程序安全性。尽管与传统的服务器会话管理相比,JWT有一定程度的复杂性,但JSON格式标准字段命名加密的签名的这些特性还是使JSON Web Tokens得到了广泛的应用。

JWT的组成元素

JWT标准定义了令牌的三个元素:headerpayloadsignature。每个元素使用Bas64编码的字符串组成,以便与HTTP头所需的ASCII字符集相兼容。序列化的令牌结构使用句点(.)字符分隔这三个元素。headerpayload元素包含一个或多个属性的JSON对象,signature元素包含了headerpayload元素的二进制签名。RFC 7519 3.1节提供了一个JWT示例,其中包括每个元素的编码和解码表示。

JWT Header

大多数JWT都包括一个带有签名算法header,该签名算法描述了加密密钥的类型哈希算法。JSON Web签名标准定义了利用基于哈希消息验证码的对称密钥算法,以及几种非对称密钥算法。两种类型的加密密钥策略都依赖于SHA-2哈希算法,其输出大小可选,分别为256、384或512位。

比如header指定使用SHA-256的对称密钥HMAC验证,可以在JSON中表示如下:

代码语言:javascript
复制
{"typ":"JWT","alg":"HS256"}

Base64编码后为

代码语言:javascript
复制
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

JWT Payload

JWT的payload包含了一些可扩展数量的属性,称之为声明。RFC 7519第4.1节定义了一套已经注册了的用于提供基本身份和有效性细节的声明(我们自定义声明时应别名于这些声明名称关键字)。具体的实现服务中的payload还可以包括自定义的声明,以提供额外的授权状态信息。

比如payload指定了一个带有用户名和过期时间戳的声明,可以使用以下JSON表示:

代码语言:javascript
复制
{"sub":"username","exp":1640995200}

Base64编码后为

代码语言:javascript
复制
eyJzdWIiOiJ1c2VybmFtZSIsImV4cCI6MTY0MDk5NTIwMH0

JWT Signature

signature提供了headerpayload的可验证签名。更改headerpayload的任何部分都会导致不同的签名。使用对称密钥或非对称密钥对的私钥生成signature,这个signature就可以(使用公钥)被用来去验证headerpayload是否被篡改,是否还是服务最初发布的原始值。RFC 7519第6节描述了不安全的jwt,其中签名元素为空字符串,签名算法为空,但是这种实现并不常见,需要额外的安全措施,并不适合大多数使用场景。

简介

Apache NiFi从0.4.0版本起就开始利用JSON Web Tokens来提供持久的用户界面访问。除了使用X.509证书TLS双向认证外,jwt还支持大多数NiFi认证策略,包括LDAPKerberosOpenID ConnectSAML。在成功交换凭证之后,NiFi服务生成并返回一个JWT, web浏览器将使用它来处理所有后续请求。这种方法将对身份提供者的影响最小化,还简化了完成登录过程后的应用程序访问。

尽管JWT的生成、签名和验证对NiFi用户或管理员并不直接可见,但这些功能对于应用程序的安全性来说是必不可少的。NiFi最近的变化改进了JWT处理的各个方面,增强了服务器和客户端处理中的应用程序安全性。这些更新涵盖了NiFi在登录处理过程中产生的所有JSON Web Tokens密钥生成密钥存储签名验证令牌撤销。在评估认证策略和考虑整体系统安全时,根据这些更新的实现来理解NiFi JWT处理还是很有用的。

实现概要

对JWT处理的更新几乎涉及到实现的每个方面,从支持库到客户机请求格式。最初的实现和更新后的实现都依赖于Spring Security来提供web应用程序安全的基础结构。

NIFI最初的JWT实现

NiFi 1.14.0和更早版本的JSON Web令牌实现包括以下特性:

  • 基于JJWT库
  • 使用随机UUID为每个经过身份验证的用户生成对称密钥
  • 在位于文件系统上的H2数据库中存储对称密钥
  • 基于HMAC和SHA-256的JWT签名验证
  • 基于删除对称密钥令牌撤销
  • Web浏览器使用HTTP Authorization头和使用本地存储(Local Storage)来存储Token

NIFI新版的JWT实现

JWT处理的更新包括以下特性:

  • 基于Spring Security OAuth 2.0 JOSE和Nimbus JOSE JWT库
  • 使用RSA算法生成非对称密钥对,密钥大小为4096位
  • 私钥存储在应用程序内存中
  • 公钥存储在持久化到文件系统的local State Provider
  • 密钥对基于可配置的持续时间进行更新,默认为1小时
  • 使用RSASSA-PSS和SHA-512进行JWT签名验证
  • 基于State Provider记录失效的令牌标识符,实现令牌撤销
  • Web浏览器使用限制JavaScript访问的HTTP会话cookie来存储Token

更新前后对比

重构NiFi JWT涉及到对nifi-web-security模块的大量代码更改,包括配置和请求处理组件。更改JWT生成和处理还提供了引入新单元测试来验证组件行为的机会。Spring Security框架的最新开发允许用标准实现替换几个自定义类。虽然NiFi没有实现OAuth 2.0规范,但更新后的JWT实现使用了几个Spring Security OAuth 2.0组件,它们提供了可配置的令牌验证。一个新的配置类将支持的组件连接在一起,各个元素使用私有变量来指定各个方面,比如键大小和处理算法。虽然一些属性可以作为NiFi应用程序属性公开,但内部默认值为所有部署提供了高级别的安全性。

使用默认值就够用了

库对比

自JWT处理在NiFi 0.4.0中首次亮相以来,就使用JJWT库实现令牌的生成、签名和验证。JJWT库里包含了大量的特性和大量的测试,而Spring Security OAuth 2.0依赖于Nimbus JOSE JWT库,后者提供了一些额外的功能,例如使用JSON Web Keys对令牌验证的简化支持。这两个库都为JWT处理提供了坚实的基础,但对于依赖于Spring Security OAuth 2.0的应用程序来说,Nimbus JOSE JWT是构建自定义功能的最直接的选择。随着Spring Security依赖的引入,包括spring-security-oauth2-resource-serverspring-security-oauth2-jose,迁移到Nimbus JOSE JWT是首选。

Spring Security OAuth 2.0库提供了许多用于实现令牌身份验证的有用组件。JwtAuthenticationProvider实现了标准的Spring Security AuthenticationProvider接口,并允许与NiFi授权组件相匹配的自定义身份验证转换策略。利用Spring Security消除了对自定义类的需要。Spring Security还提供了通用的JwtDecoderOAuth2TokenValidator接口,用于抽象令牌的解析和验证。通过可扩展和可组合的实现,Spring Security OAuth 2.0模块简化了NiFi JWT处理,并与web安全配置的其余部分自然匹配。

Nimbus库包含了几个标准接口,包括JWTProcessorJWSKeySelector,它们为声明验证和签名验证提供了扩展点。这些接口的实现支持与Spring Security OAuth 2.0组件的直接集成,还提供了针对离散特性进行单元测试的机会。Nimbus库还包括一套完整的JWT对象建模类,这使得它更容易实现特性,而无需担心直接JSON解析和序列化。

秘钥的生成对比

用于JSON Web signature生成和验证的加密密钥是实现安全性的一个基本元素。关键是要有足够的长度随机性。一个弱密钥或被破坏的密钥可能被对手获取并冒充其他用户或提供升级特权的恶意jwt。

NiFi 1.14.0之前版本使用java.util.UUID.randomUUID()为每个经过身份验证的用户生成唯一的对称密钥。为每个用户提供一个唯一的密钥可以确保一个被破坏的密钥不能用于为不同的用户生成JWT。尽管随机UUID方法生成36个字符的字符串,但有效的随机性还是要小得多。

随机UUID方法使用java.security.SecureRandom生成16个随机字节,但是UUID版本4需要使用一个字节来表示UUID版本,一个字节来表示变体,将有效的随机字节数减少到14,或122位。尽管潜在的随机值的数量仍然非常大,但按照RFC 7518 Section 3.2里的描述,122位还不到使用SHA-256的HMAC所需的最小密钥长度的一半。

更新后的JWT实现将HMAC SHA-256算法替换为基于RSA密钥对的数字签名。NiFi不是为每个用户创建一个密钥,而是生成一个密钥大小为4096位的共享密钥对。RFC 7518 Section 3.5要求使用RSASSA-PSS时密钥最小为2048位,NiFi值为4096符合当前推荐的强RSA密钥对。NiFi使用标准的Java KeyPairGenerator接口,该接口委托给已配置的Java安全提供程序,并利用SecureRandom类进行随机生成。

NiFi新版的JWT的RSA密钥对中,私钥用于生成signature,公钥要验证signature

秘钥更新周期

为了减少潜在的密钥泄露,NiFi以可配置的时间间隔生成新的密钥对,默认为1小时。nifi中的以下属性,可配置属性调整秘钥更新间隔:

代码语言:javascript
复制
nifi.security.user.jws.key.rotation.period

该属性支持使用ISO 8601标准的间隔时间,默认值为PT1H。更频繁地生成新密钥对会使用额外的计算资源,而较少频繁地更新会影响被破坏的密钥保持有效的时间长度。

JwtAuthenticationSecurityConfiguration配置类在生成bean的keyGenerationCommand()方法中,会利用Spring的ThreadPoolTaskScheduler,注册一个KeyGenerationCommand的定时任务,调度周期就是nifi.security.user.jws.key.rotation.period(默认一小时)。 KeyGenerationCommandrun方法会被调度生成秘钥对,以及一个UUID(JWT ID),然后更新内存中的私钥,将新的公钥存在Local State中。

秘钥存储的对比

最初的NiFi JWT实现将生成的对称密钥存储在位于文件系统上的H2数据库中。数据库表为每个用户建立一条记录,这条记录将生成的UUID与用户标识符关联起来。在NiFi 1.10.0之前,H2数据库在初次登录后为每个用户保留相同的UUID对称密钥。这种方法不支持任何类型的JWT撤销,依赖于过期声明来使令牌撤销。

NiFi 1.10.0发布更新后,注销用户界面删除了用户当前的对称密钥,有效地撤销了当前令牌,并强制在后续登录时生成一个新的UUID。尽管有这些改进,但还是使用了没有任何额外保护的H2数据库存储对称密钥。

更新后的实现利用非对称加密的属性,将生成的私钥公钥``分开存储。NiFi将当前的私钥保存在内存中,并将相关的公钥存储在Local State Provider中。这种方法允许NiFi在应用程序重启后仍可以使用公钥验证当前令牌,同时避免不安全的私钥存储。默认的Local State Provider将条目保存在NiFi安装目录下名为local的目录中。

私钥用于生成签名,存在内存中。公钥用于校验签名是否合法,存在Local State中。 源码StandardJwsSignerProvider中的currentSigner里存的有私钥,只在内存,无持久化。 在源码StandardVerificationKeyServicesave方法里可以看到,存到state中的是key就是所谓的JWT ID(生成秘钥对的时候同时生成的UUID),value是一个VerificationKey对象序列化后的字符串,其中包含了公钥,算法和公钥的过期时间等信息(新生成的公钥过期时间由nifi.security.user.jws.key.rotation.period配置决定,默认一小时,但后面在签名时,会被新生成的Token的过期时间所覆盖)。

签名算法的对比

基于密钥生成和密钥存储的改变,新的NiFi JWT实现使用PS512 JSON Web签名算法代替HS256(HMACSHA-256算法依赖于对称密钥来生成签名和验证,而其他算法则使用私钥进行签名,使用公钥进行验证)。由于NiFi同时充当令牌颁发者和资源服务器,HMAC SHA-256算法提供了一个可接受的实现。但是,在令牌创建和验证中使用相同的密钥,需要对敏感信息进行持久的存储,而迁移到基于非对称密钥对的算法会消除这一需求。

在技术术语中,使用HMAC SHA-256生成的JWT的签名部分不是一个加密签名,而是一个提供数据完整性度量的消息验证码。PS512算法是利用非对称密钥对的几个选项之一。RS512PS512都使用RSA密钥对,但PS512使用更新的RSA签名方案和RFC 8017 Section 8.1中的Appendix-Probabilistic Signature Scheme策略。与RSASSA-PKCS1-v1_5相比,RSASSA-PSS标准提供了更好的安全性,RSASSA-PKCS1-v1_5嵌入了一个哈希函数规范,该规范可能会被较弱的替代方案替代。尽管RFC 8017 Section 8指出,目前还没有针对支持RS512的签名策略的已知攻击,但还是推荐使用PS512算法。其他新的非对称密钥对算法也可用,如RFC 8037 3.1节中定义的Edwards-curve Ed25519,这些算法需要额外的支持库,NiFi可以考虑在未来的版本中包含这些支持库。

Token失效的对比

随着NIFI从对称密钥共享的非对称密钥对的转变,有必要引入一种新的实现令牌撤销的方法。

过期机制强制令牌拥有有限的生命周期,最长可达12小时,而令牌撤销可以确保完成注销过程后令牌不再有效。

NiFi版本1.10.01.14.0通过删除用户的对称密钥实现了有效的令牌撤销,而更新后的实现则是通过记录和跟踪被撤销的令牌标识符来实现的令牌撤销

JWT ID声明提供了标识唯一令牌的标准方法。在令牌生成期间,NiFi分配一个随机UUID作为JWT ID。当用户发起注销过程时,NiFi记录下这个对应的JWT ID,NiFi根据记录的JWT ID拒绝未来的请求,这种方式使NiFi能够处理令牌发放和令牌失效之间的间隔状态。JWT ID的记录依赖于NiFiLocal State Provider,在重启时会被清理一遍(清理那些过期的)。这种撤销策略只存储最少的信息,更加细粒度的使用了标准的JWT属性。同时NiFi使用可配置的秘钥更新周期来查找和删除过期的失效记录。

令牌失效有两种,一种是令牌过期,一种是用户发起注销引起的令牌撤销。 前文提及,公钥存储在Local State,key就是JWT ID,value是一个对象序列化后的字符串,里面包含了公钥的过期时间。源码JwtAuthenticationSecurityConfiguration配置类在生成bean的keyExpirationCommand()方法中,会利用Spring的ThreadPoolTaskScheduler,注册一个KeyExpirationCommand的定时任务,调度周期就是nifi.security.user.jws.key.rotation.period(默认一小时)。KeyExpirationCommand会调用StandardVerificationKeyServicedeleteExpired()方法,用来清理过期的公钥记录。 【注意】:虽然公钥有过期时间(默认一小时),会被定时清理,但是这个过期时间会在生成Token时被Token中的过期时间覆盖,比如生成的token默认过期时间12小时,则公钥的过期时间也会更新成12小时。而每次生成的JWT ID不同,Local State(可以简单理解成一个map)中是可以同时存在多个时段的公钥信息。举个形象点的例子,NIFI启动后生成了一个共享的秘钥对,其中公钥存储到了Local State中,过期时间是默认值一小时(假定我们没有修改nifi.security.user.jws.key.rotation.period)。过了40分钟后,此时公钥过期时间还剩下20分钟,然后用户张三登陆了NiFi,NIFI程序验证通过了张三的用户名和密码后,要生成并返回JWT,假定生成的Token的过期时间是12小时,其中在生成signature的时候会将这个12小时的过期时间更新在当前的公钥存储里,于是乎此时公钥过期时间变成了12小时。然后再过20分钟(满一小时了),NIFI程序自动生成了新的秘钥对,内存中的私钥被替换成了新的,Local State中增加了新的公钥,即张三登陆时拿到的那个Token所对应的所需要的公钥还在Local State中。 用户完成登出过程后程序会调用StandardJwtLogoutListenerlogout(final String bearerToken)方法,方法中会调用StandardJwtRevocationServicesetRevoked(final String id, final Instant expiration)方法,将当前的JWT ID记录下来,下一次这个Token发起请求的时候就会拒绝访问。同理公钥存储的过期清理的定时任务,JWT ID也有定时任务进行过期清理,这里不赘述。

浏览器

在JWT处理的最初实现中,NiFi使用HTTP Authorization header传递令牌,使用RFC 6750 Section 2.1中定义的Bearer方案。在成功交换凭证之后,NiFi用户界面使用Local Storage存储JWT进行持久访问。基于令牌寿命和跨浏览器实例的持久存储,用户界面维护一个经过身份验证的会话,而不需要额外的访问凭据请求。该接口还利用令牌的存在来指示是否显示登出链接。

本地存储的问题

使用标准HTTP Authorization header提供了在后续请求中传递JWT的直接方法,但是利用Local Storage会引起关于令牌本身安全性的潜在问题。浏览器Local Storage在应用程序重新启动时持续存在,如果用户在没有完成NiFi注销过程的情况下关闭浏览器,令牌将保持持久性,并可用于未来的浏览器会话。而在NiFi用户界面中执行的所有JavaScript代码都可以使用本地存储,可能导致NIFI受到跨站点脚本攻击。基于这些原因,Web应用程序安全方面建议不要将任何敏感信息持久化到Local Storage

除了潜在的安全问题外,使用Local Storage还会在不同的浏览器实例中访问应用程序资源。NiFi内容查看器等特性需要实现自定义的一次性密码身份验证策略,当浏览器试图加载高级用户界面扩展的资源时,也会导致访问问题。

Local Storage:https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage:Also known as Offline Storage, Web Storage. Underlying storage mechanism may vary from one user agent to the next. In other words, any authentication your application requires can be bypassed by a user with local privileges to the machine on which the data is stored. Therefore, it's recommended to avoid storing any sensitive information in local storage where authentication would be assumed.

基于Cookie的实现

为了解决安全和可用性问题,NiFi最近的更新使用了HTTP会话cookie替换了JWTLocal Storage。会话cookie实现使用HttpOnly属性来限制访问,使其对JavaScript不可用,这缓解了一些潜在的漏洞。浏览器在重新启动时不维护会话cookie,这避免了与有效或陈旧令牌的持久性相关的问题。

新的实现使用了SameSite属性的Strict设置,该设置指示支持浏览器避免在第三方站点发起的请求中发送cookie。会话cookie还使用Cookie Name Prefixes来通知支持它的浏览器,cookie必须包含Secure属性,要求在后续的请求中传输使用HTTPS。

由于JavaScript对HTTP会话cookie的访问限制,更新后的实现还采用了一种不同的方法来注销支持状态。NiFi用户界面将过期时间戳存储在Session Storage中,而不是将整个令牌存储在Local Storage中。与会话cookie类似,浏览器在关闭时从Session Storage中删除项目。此策略依赖于存储最小数量的信息,且使用寿命较短,从而避免了与令牌本身相关的安全问题和潜在的持久性问题。

总结

NiFi中的JSON Web Tokens并不是Web应用程序安全最明显的方面,但它们在许多部署配置中起到了至关重要的作用。作为一个顶级的开源项目,开发一个最佳的JWT实现需要考虑许多因素。NiFi 0.4.0中JWT支持的最初部署解决了各种用例,但技术进步和最近的库开发为改进实现提供了几个机会。更新后的JWT集成增强了服务器和浏览器代码中的安全性,为潜在的和理论上的攻击提供了额外的保护。web应用安全的大部分方面都需要不断的评估,NiFi JWT支持也不例外。

关于请求NIFI后端API,以表单形式将username password(application/x-www-form-urlencoded)Post到https://XX:8443/access/token,返回token然后增加到Cookie(name是__Secure-Authorization-Bearer)。 如果想避免到NIFI界面登陆,直接重定向到流程,同域的还好说,将token添加到cookie中就好了,而如果是跨域就有些麻烦了。跨域的话最直接的方式就是反向代理(比如nginx)NIFI的地址,使与自定义的web应用同域。还有一种稍微复杂点的需要开发的操作,我是这么干的,我自定义了一套无侵入源码NIFI的多用户多租户的登陆以及授权(一个nar),在NIFI免安全认证开放一个Get请求API(自定义的无侵入源码的war),向这个API传递token和groupId参数,然后在NIFI程序里设置cookie并重定向,最后这种方案有时间的话再写篇文章进行说明。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Panda诚 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文目的
  • 背景知识
  • JWT的组成元素
    • JWT Header
      • JWT Payload
        • JWT Signature
        • 简介
        • 实现概要
          • NIFI最初的JWT实现
            • NIFI新版的JWT实现
            • 更新前后对比
              • 库对比
                • 秘钥的生成对比
                  • 秘钥更新周期
                • 秘钥存储的对比
                  • 签名算法的对比
                    • Token失效的对比
                      • 浏览器
                        • 本地存储的问题
                        • 基于Cookie的实现
                    • 总结
                    相关产品与服务
                    对象存储
                    对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档