策略执行点 (PEP) 是一种设计模式,因此您可以通过不同的方式实现它。Keycloak 提供了所有必要的工具来为不同的平台、环境和编程语言实现 PEP。Keycloak 授权服务提供 RESTful API,并利用 OAuth2 授权功能,使用集中式授权服务器进行细粒度授权。
PEP 负责从 Keycloak 服务器执行访问决策,这些决策通过评估与受保护资源相关的策略来做出。它充当应用程序中的过滤器或拦截器,用于检查是否可以根据这些决策授予的权限,满足对受保护资源的特定请求。
Keycloak 为启用 **Keycloak 策略执行器** 到 Java 应用程序提供了内置支持,并具有内置支持来保护符合 JakartaEE 标准的框架和 Web 容器。如果您使用的是 Maven,则应将以下依赖项配置到您的项目中
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-policy-enforcer</artifactId>
<version>26.0.2</version>
</dependency>
启用策略执行器后,发送到应用程序的所有请求都会被拦截,并且根据 Keycloak 授予执行请求的身份的权限,授予对受保护资源的访问权限。
策略执行器配置使用 JSON 格式,如果您想根据资源服务器提供的资源自动解析受保护的路径,大多数情况下您不需要设置任何内容。
如果您想手动定义要保护的资源,您可以使用更详细的格式
{
"enforcement-mode" : "ENFORCING",
"paths": [
{
"path" : "/users/*",
"methods" : [
{
"method": "GET",
"scopes" : ["urn:app.com:scopes:view"]
},
{
"method": "POST",
"scopes" : ["urn:app.com:scopes:create"]
}
]
}
]
}
以下是每个配置选项的描述
enforcement-mode
指定如何执行策略。
ENFORCING
(默认模式) 默认情况下拒绝请求,即使没有与给定资源关联的策略。
PERMISSIVE
即使没有与给定资源关联的策略,也允许请求。
DISABLED
完全禁用策略评估,并允许访问任何资源。当 enforcement-mode
为 DISABLED
时,应用程序仍然可以通过 授权上下文 获取 Keycloak 授予的所有权限
on-deny-redirect-to
定义一个 URL,当从服务器获取“拒绝访问”消息时,客户端请求将被重定向到该 URL。默认情况下,适配器会响应 403 HTTP 状态码。
path-cache
定义策略执行器应如何跟踪应用程序中的路径与 Keycloak 中定义的资源之间的关联。缓存是必要的,以避免通过缓存路径和受保护资源之间的关联来避免不必要的 Keycloak 服务器请求。
lifespan
定义条目应过期的毫秒数。如果未提供,默认值为 **30000**。可以设置等于 0 的值以完全禁用缓存。可以设置等于 -1 的值以禁用缓存的过期。
max-entries
定义应保存在缓存中的条目限制。如果未提供,默认值为 **1000**。
paths
指定要保护的路径。此配置是可选的。如果未定义,策略执行器将通过获取您在 Keycloak 中定义的应用程序的资源来发现所有路径,其中这些资源定义了表示应用程序中某些路径的 URIS
。
name
服务器上要与给定路径关联的资源的名称。与 **path** 一起使用时,策略执行器将忽略资源的 **URIS** 属性,并使用您提供的路径。
path
(必需) 相对于应用程序上下文路径的 URI。如果指定了此选项,策略执行器将查询服务器以获取具有相同值的 **URI** 的资源。目前支持非常基本的路径匹配逻辑。有效路径的示例如下:
通配符: /*
后缀: /*.html
子路径: /path/*
路径参数: /resource/{id}
精确匹配: /resource
模式: /{version}/resource, /api/{version}/resource, /api/{version}/resource/*
methods
要保护的 HTTP 方法(例如 GET、POST、PATCH)以及它们如何与服务器中给定资源的范围关联。
method
HTTP 方法的名称。
scopes
一个包含与方法关联的范围的字符串数组。当您将范围与特定方法关联时,尝试访问受保护资源(或路径)的客户端必须提供一个 RPT,该 RPT 授予对列表中指定的所有范围的权限。例如,如果您定义了一个具有范围 create 的方法 POST,则当对路径执行 POST 时,RPT 必须包含一个授予对 create 范围的访问权限的权限。
scopes-enforcement-mode
一个字符串,引用与方法关联的范围的执行模式。值可以是 **ALL** 或 **ANY**。如果为 **ALL**,则必须授予所有已定义的范围才能使用该方法访问资源。如果为 **ANY**,则必须授予至少一个范围才能使用该方法获得对资源的访问权限。默认情况下,执行模式设置为 **ALL**。
enforcement-mode
指定如何执行策略。
ENFORCING
(默认模式) 默认情况下拒绝请求,即使没有与给定资源关联的策略。
DISABLED
claim-information-point
定义一组或多组必须解析并推送到 Keycloak 服务器的声明,以便使这些声明可供策略使用。有关详细信息,请参阅 声明信息点。
lazy-load-paths
指定适配器应如何获取服务器以获取与应用程序中的路径关联的资源。如果为 **true**,策略执行器将根据请求的路径按需获取资源。此配置在您不想在部署期间从服务器获取所有资源(如果您没有提供任何 paths
)或如果您只定义了一部分 paths
并想按需获取其他资源的情况下特别有用。
http-method-as-scope
指定应如何将范围映射到 HTTP 方法。如果设置为 **true**,策略执行器将使用当前请求中的 HTTP 方法来检查是否应授予访问权限。启用后,确保您的 Keycloak 中的资源与表示您要保护的每个 HTTP 方法的范围相关联。
claim-information-point
定义一组或多组必须解析并推送到 Keycloak 服务器的 **全局** 声明,以便使这些声明可供策略使用。有关详细信息,请参阅 声明信息点。
声明信息点 (CIP) 负责解析声明并将这些声明推送到 Keycloak 服务器,以便为策略提供有关访问上下文的更多信息。它们可以作为策略执行器的配置选项来定义,以便从不同来源解析声明,例如
HTTP 请求(参数、标头、主体等)
外部 HTTP 服务
在配置中定义的静态值
通过实现声明信息提供者 SPI 的任何其他来源
将声明推送到 Keycloak 服务器时,策略不仅可以根据用户的身份做出决策,还可以根据给定事务的 who、what、why、when、where 和 which,将上下文和内容考虑在内。它与基于上下文的授权以及如何在运行时使用信息来支持细粒度授权决策有关。
以下是一些示例,展示如何从 HTTP 请求中提取声明
{
"paths": [
{
"path": "/protected/resource",
"claim-information-point": {
"claims": {
"claim-from-request-parameter": "{request.parameter['a']}",
"claim-from-header": "{request.header['b']}",
"claim-from-cookie": "{request.cookie['c']}",
"claim-from-remoteAddr": "{request.remoteAddr}",
"claim-from-method": "{request.method}",
"claim-from-uri": "{request.uri}",
"claim-from-relativePath": "{request.relativePath}",
"claim-from-secure": "{request.secure}",
"claim-from-json-body-object": "{request.body['/a/b/c']}",
"claim-from-json-body-array": "{request.body['/d/1']}",
"claim-from-body": "{request.body}",
"claim-from-static-value": "static value",
"claim-from-multiple-static-value": ["static", "value"],
"param-replace-multiple-placeholder": "Test {keycloak.access_token['/custom_claim/0']} and {request.parameter['a']}"
}
}
}
]
}
以下是一些示例,展示如何从外部 HTTP 服务中提取声明
{
"paths": [
{
"path": "/protected/resource",
"claim-information-point": {
"http": {
"claims": {
"claim-a": "/a",
"claim-d": "/d",
"claim-d0": "/d/0",
"claim-d-all": [
"/d/0",
"/d/1"
]
},
"url": "http://mycompany/claim-provider",
"method": "POST",
"headers": {
"Content-Type": "application/x-www-form-urlencoded",
"header-b": [
"header-b-value1",
"header-b-value2"
],
"Authorization": "Bearer {keycloak.access_token}"
},
"parameters": {
"param-a": [
"param-a-value1",
"param-a-value2"
],
"param-subject": "{keycloak.access_token['/sub']}",
"param-user-name": "{keycloak.access_token['/preferred_username']}",
"param-other-claims": "{keycloak.access_token['/custom_claim']}"
}
}
}
}
]
}
{
"paths": [
{
"path": "/protected/resource",
"claim-information-point": {
"claims": {
"claim-from-static-value": "static value",
"claim-from-multiple-static-value": ["static", "value"]
}
}
}
]
}
如果内置提供者都不足以满足开发人员的要求,声明信息提供者 SPI 可用于支持不同的声明信息点。
例如,要实现新的 CIP 提供者,您需要实现 org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory
和 ClaimInformationPointProvider
,并在应用程序的类路径中提供文件 META-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory
。
org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory
的示例
public class MyClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<MyClaimInformationPointProvider> {
@Override
public String getName() {
return "my-claims";
}
@Override
public void init(PolicyEnforcer policyEnforcer) {
}
@Override
public MyClaimInformationPointProvider create(Map<String, Object> config) {
return new MyClaimInformationPointProvider(config);
}
}
每个 CIP 提供者都必须与一个名称关联,如上面在 MyClaimInformationPointProviderFactory.getName
方法中定义的那样。此名称将用于将 policy-enforcer
配置中 claim-information-point
部分的配置映射到实现。
处理请求时,策略执行器将调用 MyClaimInformationPointProviderFactory.create 方法以获取 MyClaimInformationPointProvider 的实例。调用时,将任何为此特定 CIP 提供者(通过 claim-information-point)定义的配置作为映射传递。
ClaimInformationPointProvider
的示例
public class MyClaimInformationPointProvider implements ClaimInformationPointProvider {
private final Map<String, Object> config;
public MyClaimInformationPointProvider(Map<String, Object> config) {
this.config = config;
}
@Override
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
Map<String, List<String>> claims = new HashMap<>();
// put whatever claim you want into the map
return claims;
}
}
启用策略执行后,从服务器获取的权限可通过 org.keycloak.AuthorizationContext
获得。此类提供了多种方法,您可以使用这些方法来获取权限并确定是否已为特定资源或范围授予权限。
在 Servlet 容器中获取授权上下文
HttpServletRequest request = // obtain javax.servlet.http.HttpServletRequest
AuthorizationContext authzContext = (AuthorizationContext) request.getAttribute(AuthorizationContext.class.getName());
授权上下文有助于您更好地控制服务器做出的决策。例如,您可以使用它来构建动态菜单,其中项目根据与资源或范围关联的权限隐藏或显示。 |
if (authzContext.hasResourcePermission("Project Resource")) {
// user can access the Project Resource
}
if (authzContext.hasResourcePermission("Admin Resource")) {
// user can access administration resources
}
if (authzContext.hasScopePermission("urn:project.com:project:create")) {
// user can create new projects
}
AuthorizationContext
代表了 Keycloak 授权服务的主要功能之一。从上面的示例可以看出,受保护资源并未直接与管理它们的策略相关联。
考虑一些使用基于角色的访问控制 (RBAC) 的类似代码
if (User.hasRole('user')) {
// user can access the Project Resource
}
if (User.hasRole('admin')) {
// user can access administration resources
}
if (User.hasRole('project-manager')) {
// user can create new projects
}
尽管这两个示例都解决了相同的要求,但它们使用的是不同的方法。在 RBAC 中,角色仅 *隐式* 定义对资源的访问权限。使用 Keycloak,您可以获得创建更易于管理的代码的能力,该代码直接关注您的资源,无论您是使用 RBAC、基于属性的访问控制 (ABAC) 还是任何其他 BAC 变体。您要么拥有对给定资源或范围的权限,要么没有该权限。
现在,假设您的安全要求已更改,除了项目经理,PMO 也能创建新项目。
安全要求会发生变化,但使用 Keycloak,您无需更改应用程序代码来满足新的要求。一旦您的应用程序基于资源和范围标识符,您只需要更改授权服务器中与特定资源关联的权限或策略的配置即可。在这种情况下,将更改与 项目资源
和/或范围 urn:project.com:project:create
关联的权限和策略。
也可用于获取对配置到应用程序的 Keycloak 授权客户端 的引用AuthorizationContext
ClientAuthorizationContext clientContext = ClientAuthorizationContext.class.cast(authzContext);
AuthzClient authzClient = clientContext.getClient();
在某些情况下,由策略执行器保护的资源服务器需要访问授权服务器提供的 API。有了 AuthzClient
实例,资源服务器可以与服务器交互以创建资源或以编程方式检查特定权限。
当服务器使用 HTTPS 时,请确保您的策略执行器按如下方式配置
{
"truststore": "path_to_your_trust_store",
"truststore-password": "trust_store_password"
}
以上配置启用授权客户端的 TLS/HTTPS,使您可以使用 HTTPS 方案远程访问 Keycloak 服务器。
强烈建议您在访问 Keycloak 服务器端点时启用 TLS/HTTPS。 |