1.9.虚之墙:安全

木落如飞鸟,山平疑澹烟。灯残挥手去,曳杖听流泉。——陈继儒《同印空夜坐凭虚阁》

「壹」基本开发

    本文介绍Zero中关于安全部分的开发,该部分开发主要使用配置实现,而且Zero框架中所有和认证授权相关的内容都写在AOP层,AOP全称为Aspect Oriented Program,面向切面编程,一般通过预编译方式和运行时动态代理实现程序功能的统一维护技术,它属于OOP的延续。

1.1. 基本配置

    Zero框架中的配置文件结构如下:

    上述结构中注意两个根节点:

节点名
含义

type

当前安全框架实现的类型,目前版本jwt

config

和安全实现相关的配置数据,不同类型配置数据有所区别。

    由于项目原因,目前Zero只支持比较主流的jwt类型,zero-rbac中提供的默认安全实现也是基于jwt的,为了强化jwt,旧版本Zero中的basicmongo 两种类型目前都不考虑,如果您想要自己实现,可参考下边两个类去自定义:

  • io.vertx.up.secure.handler.JwtOstium(AuthHandler子接口)

  • io.vertx.up.secure.provider.authenticate.JwtAuth(AuthProvider子接口)

1.2. 跨域

    跨域配置是Zero和Zero-UI前后端集成时的核心配置,该配置同样采用第三方集成配置,文件结构参考上图,文件内容如下:

这个配置就没有必要解释了,读者一看就明白。

1.3. @Wall

    言归正传,接下来看看Zero中安全部分的核心开发,在看这部分之前先区分几个基本工作流:

  1. 登录流程:根据用户账号和口令生成令牌(Token),该令牌会在后续请求过程中追加到Authorization请求头中。

  2. 认证流程:(401)触发系统的安全框架验证Authorization请求头中提供的令牌Token是否合法。

  3. 授权流程:(403)认证成功后,触发系统安全框架中的核心授权逻辑。

    综上,您开发的任何一个系统前两个流程是可以标准化的,比如JWT算法生成令牌、基于OAuth的三步骤交换令牌、或者在Basic认证中检查用户名和密码一步到位等等,而最后一个授权流程就根据系统需求有所差异了,您可以基于RBAC模型来设计系统的授权流程(后续教程会带您分析zero-rbac 子模块的授权系统)。

1.3.1. Security接口

    io.vertx.up.secure.Security接口是Zero配合认证授权设计的核心接口,它的代码定义如下:

1.3.2. 开发步骤

    Zero使用了vertx-auth-common 框架执行安全流程,并根据Vert.x的原始代码进行重新设计以及开发,创建了自定义可配置的安全模块,从前文可知,Zero重写了AuthProviderAuthHandler,重写过后支持下边功能:

  1. 和Security接口连接,实现标准化的认证、授权流程。

  2. 将权限认证转移到切面代码中,使用Annotation注解技术实现AOP层的认证。

  3. 让Zero可启用插件模块对授权进行强定制(zero-rbac中数据域实现)。

    先看一个基本示例:

Agent代码

Security实现类

    注意:上边代码主要是为了测试,所以只重写了认证的方法,并没有提供其他实现。

@Wall主类

    此处的基本思路和vertx-auth-common模块扩展思路一样,都是创建自定义的AuthHandlerAuthProvider ,而Zero只是将这几部分内容通过轻量级的方式编连到一起,上述代码的内部实现处于切面层,所以它们的存在不影响Agent/Worker结构。

    @Wall注解,单词翻译为“墙”,它是Zero中打开安全模块的注解,内部定义的注解方法如下:

方法名
含义

value()

默认方法,用于描述安全类型,目前可用值为jwt,若要添加其他模式,可扩展开发Zero。

path()

设置执行该墙的路径,默认为/*,示例中使用了/api/*

order()

墙检测顺序,当您拥有多个@Wall定义时使用,可在同一个系统中支持多种安全模式。

define()

是全扩展开发还是半扩展开发,半扩展开发依赖@Phase的定义。

    运行上述案例代码,任意发送到/api/*下的请求都会收到如下响应:

    并且在后台会看到如下信息:

    如何将请求发送成功呢?需在客户端增加Authorization的HTTP请求头重新发送请求,这个例子中由于Security类的verity方法直接返回了ture,任意格式合法的JwtToken都可以得到最终的正确请求:

1.3.3. 工具类

    为了方便用户将信息存储到JWT的令牌并执行提取,Zero提供了核心工具类Ux.Jwt,先看下边代码:

Agent代码

    上述代码请求会返回一个当前环境中的令牌值:

    运行上述代码需在您的环境中做一定的准备:

  1. 安全配置文件中的配置如下:

  2. 在该配置下,需在src/main/resources中追加keys/keystore.jceks文件,该文件为自己生成,如果读者嫌麻烦可直接下载代码找到该文件。

  3. IDEA运行时,运行截图如下:

    若不做上边三个准备,会遇到下边的错误:

    如代码中所示,Zero中提供的工具函数如下:

    上述准备代码就是为工具类服务的,主要目的如下:

  1. 生成Jwt的Token时,保证算法的基本条件满足,这部分内容可参考Jwt的Token算法相关资料,如此才可保证Token可正确生成。

  2. 两个工具类执行的底层Jwt配置必须一致,如果算法不一致就会导致前边Algorithm not supported错。

有了工具类,您就很容易将数据存储到Token中,并且很容易从Token中提取数据。

「贰」插件扩展

    Zero中的认证授权逻辑主要基于Security接口,本章节抛开第一节提及的@Wall开发细节,转移到两个核心数据插件中,这两个插件可帮助您完成如下任务:

  1. 支持授权过后的数据域操作。

  2. 支持Auditor责任数据的切面注入。

    配置扩展插件的代码如下:

    两个插件对流程的影响如下:

2.1. 数据域插件

    数据域插件的实现可参考io.vertx.tp.rbac.extension.DataRegion源代码,它的基本结构如下:

    我在开发过程中提供的实现是基于zero-rbac模块的,数据域插件主要完成:

  1. 根据配置文件中的prefix判断哪些请求需执行数据域功能。

  2. before流程中,对请求的修改

    • 修改查询条件,根据权限定义缩小查询范围(行筛选)。

    • 修改列过滤信息,根据权限定义缩小查询范围(列筛选)。

  3. after流程中,对响应再次修改

    • 行二次过滤(区域筛选、单行筛选、动态筛选)。

    • 列二次过滤。

    • 提供模型中合法的acl信息(动态计算),创建多状态表单和多状态列表

    上述几点只是目前zero-rbac模块中已实现的基础数据域的功能,也只能作为您的一个参考,而位于切面层的代码让您更容易对请求和响应数据执行修改以及限制,zero-rbac 的详细功能后边我会专程写一篇文章来讲解,包括配置步骤,动态资源访问器和界面配置等。

2.2. Auditor插件

    Auditor插件的实现可参考io.vertx.tp.rbac.extension.AuditorPin源代码,它的基本结构如下:

    Auditor插件主要用于修改请求数据(不处理响应),在CRUD模型中,只有新增更新 会使用该插件,该插件负责让用户在系统中留下操作痕迹,至于痕迹如何存储就在于您如何设计这两个插件的内置逻辑。

    zero-rbac中提供了最简单的Auditor数据如下:

属性名
追加条件
含义

createdAt

添加数据

创建时间

createdBy

添加数据

创建人

updatedAt

添加/更新数据

更新时间

updatedBy

添加/更新数据

更新人

    执行了该插件过后,上述四个属性会被直接追加到Envelop的统一模型中,当然若您不使用zero-rbac ,也可以自己去实现,插件对请求和响应的修改是AOP模式,若不提供这两个插件,不影响正常数据流的运行。

「叄」小结

    到这里,Zero安全部分的开发就告一段落,虽然部分内容和安全没有直接关系,但这些东西都是实战过程中有用的部分,可提供给您参考。若您使用了zero-rbac 扩展模块,无疑可以直接忽略掉系统的认证、授权并拥有它所有的功能,包括在微服务环境下提供的认证授权服务器的功能。

    最后,提供一段zero-rbac的@Wall完整代码(不包含服务类),让您对本章内容有所回顾。

Last updated