在本教程中,我们将展示如何将 Spring Security 的授权决策外部化到 OPA——开放策略代理。
跨应用程序的一个共同要求是能够根据策略做出某些决定。当这个策略足够简单并且不太可能改变时,我们可以直接在代码中实现这个策略,这是最常见的场景。
但是,在其他情况下,我们需要更大的灵活性。访问控制决策是典型的:随着应用程序变得越来越复杂,授予对给定功能的访问权限可能不仅取决于您是谁,还取决于请求的其他上下文方面。这些方面可能包括 IP 地址、时间和登录身份验证方法(例如:“记住我”、OTP)等。
此外,将上下文信息与用户身份相结合的规则应该易于更改,最好不会导致应用程序停机。这一要求自然会导致一个专用服务处理策略评估请求的架构。
这种灵活性的代价是在调用外部服务时增加了复杂性和性能损失。另一方面,我们可以在不影响应用程序的情况下发展甚至完全替换授权服务。此外,我们还可以与多个应用程序共享这个服务,从而在它们之间实现一致的授权模式。
什么是OPA
开放政策代理,简称OPA,是一个用Go开源实现的策略评估引擎。它最初是由Styra开发的,现在是CNCF的一个毕业项目。下面是这个工具的一些典型用途的清单。
- Envoy授权过滤器
- Kubernetes准入控制器
- Terraform计划评估
安装OPA是非常简单的。只要下载适合我们平台的二进制文件,把它放在操作系统PATH的一个文件夹里,就可以了。我们可以用一个简单的命令来验证它是否正确安装。
$ opa version |
OPA评估用REGO编写的策略,REGO是一种经过优化的声明性语言,用于运行对复杂对象结构的查询。这些查询的结果然后由客户应用程序根据具体的使用情况来使用。在我们的案例中,对象结构是一个授权请求,我们将使用策略来查询结果,以授予对特定功能的访问。
需要注意的是,OPA的策略是通用的,不以任何方式与表达授权决策相联系。事实上,我们可以在其他传统上由Drools等规则引擎主导的场景中使用它。
编写策略
这是用REGO编写的一个简单的授权策略的样子:
package baeldung.auth.account |
首先要注意的是包的声明。OPA策略使用包来组织规则,它们在评估传入的请求时也起着关键作用,我们将在后面展示。我们可以在多个目录下组织策略文件。
接下来,我们定义实际的策略规则。
- 一个默认的规则,以确保我们最终总会得到一个授权变量的值
- 主聚合器规则,我们可以理解为 "当没有拒绝访问的规则和至少有一个允许访问的规则时,授权为真"
- 允许和拒绝规则,每一条都表达了一个条件,如果匹配,将分别在允许或拒绝数组中增加一个条目。
对OPA策略语言的完整描述超出了本文的范围,但规则本身并不难读。在看这些规则时,有几件事要记住。
- 形式为a :=b或a=b的语句是简单的赋值(不过它们不一样)。
- a = b { ......conditions }或a { ......conditions }形式的语句意味着 "如果条件为真,则将b分配给a”
- 在策略文件中的顺序出现是不相关的
除此之外,OPA还有一个丰富的内置函数库,为查询深度嵌套的数据结构进行了优化,同时还有更多熟悉的功能,如字符串操作、集合等等。
评估策略
让我们使用上一节中定义的策略来评估一个授权请求。在我们的例子中,我们将使用一个包含传入请求的一些片段的JSON结构来建立这个授权请求。
{ |
请注意,我们已经将请求属性包装在一个单一的输入对象中。这个对象在策略评估过程中成为输入变量,我们可以用类似JavaScript的语法来访问它的属性。
为了测试我们的策略是否像预期的那样工作,让我们在本地以服务器模式运行OPA,并手动提交一些测试请求。
$ opa run -w -s src/test/rego
选项-s可以在服务器模式下运行,而-w可以自动重载规则文件。src/test/rego是包含我们样本代码中策略文件的文件夹。一旦运行,OPA将在本地端口8181监听API请求。如果需要,我们可以使用-a选项改变默认端口。
现在,我们可以使用curl或其他工具来发送请求。
$ curl --location --request POST 'http://localhost:8181/v1/data/baeldung/auth/account' \ |
注意/v1/data前缀后面的路径部分。它与策略的包名相对应,点被正斜线取代。
响应将是一个JSON对象,包含针对输入数据评估策略所产生的所有结果。
{ |
结果属性是一个包含由策略引擎产生的结果的对象。我们可以看到,在这种情况下,授权属性是假的。我们还可以看到,allow 和 deny 是空数组。这意味着没有特定的规则与输入相匹配。因此,主要的授权规则也没有匹配。
Spring Authorization Manager集成
现在我们已经看到了OPA的工作方式,我们可以继续前进,把它集成到Spring授权框架中。在这里,我们将专注于它的反应式Web变体,但一般的想法也适用于基于MVC的常规应用。
首先,我们需要实现ReactiveAuthorizationManager Bean,它使用OPA作为其后台。
@Bean |
在这里,注入的WebClient来自另一个Bean,我们从@ConfigurationPropreties类中预先初始化其属性。
处理管道委托给toAuthorizationRequest方法的职责是从当前的Authentication和AuthorizationContext中收集信息,然后建立一个授权请求的有效载荷。同样地,toAuthorizationDecision也会获取授权响应,并将其映射为一个AuthorizationDecision。
现在,我们使用这个Bean来构建一个SecurityWebFilterChain:
@Bean |
我们只将我们自定义的AuthorizationManager应用于/account API。这种方法背后的原因是,我们可以很容易地扩展这个逻辑以支持多个策略文件,从而使它们更容易维护。例如,我们可以有一个配置,使用请求URI来选择一个合适的规则包,并使用这些信息来建立授权请求。
在我们的案例中,/account API本身只是一个简单的控制器/服务对,它返回一个用假余额填充的账户对象。
测试
最后但并非最不重要的是,让我们建立一个集成测试,把所有东西放在一起。首先,让我们确保 "快乐路径 "的工作。这意味着,给定一个认证的用户,他们应该能够访问自己的账户。
@Test |
其次,我们还必须验证,一个经过认证的用户应该只能访问他们自己的账户。
@Test |
最后,让我们也测试一下认证用户没有权限的情况。
@Test |
我们可以从 IDE 或命令行运行这些测试。请注意,无论哪种情况,我们都必须首先启动指向包含我们的授权策略文件的文件夹的 OPA 服务器。
结论
在本文中,我们展示了如何使用 OPA 将基于 Spring Security 的应用程序的授权决策外部化。像往常一样,完整的代码可以在 GitHub 上找到。