Authorization of Web Endpoints

Quarkus有一个内置的可插拔的web安全层。如果安全功能被启用,所有的HTTP请求将会进行权限检查,以确保它们被允许继续操作。

Configuration authorization checks are executed before any annotation-based authorization check is done, so both checks have to pass for a request to be allowed. This means you cannot use @PermitAll to open up a path if the path has been blocked using quarkus.http.auth. configuration. If you are using JAX-RS you may want to consider using the quarkus.security.jaxrs.deny-unannotated-endpoints or quarkus.security.jaxrs.default-roles-allowed to set default security requirements instead of HTTP path level matching, as these properties can be overridden by annotations on an individual endpoint.

授权是基于security provider所提供的用户角色来进行的。为了自定义这些角色,可以创建一个 SecurityIdentityAugmentor 。请参考 Security Identity Customization.

使用配置进行授权

默认实现允许您使用 application.properties 中的配置来定义权限。下面是一个配置的例子:

quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin                      (1)

quarkus.http.auth.permission.roles1.paths=/roles-secured/*,/other/*,/api/*          (2)
quarkus.http.auth.permission.roles1.policy=role-policy1

quarkus.http.auth.permission.permit1.paths=/public/*                                (3)
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET

quarkus.http.auth.permission.deny1.paths=/forbidden                                 (4)
quarkus.http.auth.permission.deny1.policy=deny
1 定义了一个基于角色的策略来授权给具有 useradmin 角色的用户。该配置会被后面的规则所引用。
2 这是一个引用之前定义的策略的权限集。 roles1 是一个任意的名字,您可以随意的命名该权限集。
3 这个权限引用了默认的 permit 内置策略,允许 GET 方法访问 /public 。在这个例子中,这实际上是一个空操作,因为这个请求无论如何都会被允许。
4 这个权限引用了内置的 deny 策略来作用于 /forbidden 。这是一个精确的路径匹配,因为它没有以 * 结尾。

权限是在配置中使用权限集来定义。他们是任意命名的权限分组。每个权限集必须指定一个用于控制访问的策略。有三种内置策略。 deny , permitauthenticated ,分别是允许所有、拒绝所有和只允许认证的用户。

也可以定义基于角色的策略,如例子所示。这些策略将只允许具有指定角色的用户访问资源。

路径、方法的匹配

权限集也可以用逗号分隔的列表来指定路径和方法。如果一个路径以 * 结尾,那么它被认为是通配符匹配,将匹配所有的子路径,否则它是精确匹配,只匹配这个特定路径:

quarkus.http.auth.permission.permit1.paths=/public/*,/css/*,/js/*,/robots.txt
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD

匹配路径但不匹配方法

如果一个请求匹配了一个或多个基于路径的权限集,但由于方法不匹配,那么该请求将被拒绝。

Given the above permission set, GET /public/foo would match both the path and method and thus be allowed, whereas POST /public/foo would match the path but not the method and would thus be rejected.

匹配多条路径:最长的路径获胜

匹配总是在最长路径胜利的基础上进行,如果已经匹配了更具体的权限集,则不考虑其他模糊的权限集:

quarkus.http.auth.permission.permit1.paths=/public/*
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD

quarkus.http.auth.permission.deny1.paths=/public/forbidden-folder/*
quarkus.http.auth.permission.deny1.policy=deny
Given the above permission set, GET /public/forbidden-folder/foo would match both permission sets' paths, but because it matches the deny1 permission set’s path on a longer match, deny1 will be chosen and the request will be rejected.

子路径权限总是赢过根路径权限,正如上面 deny1permit1 权限的例子所解释的那样。下面是另一个例子,显示了子路径权限允许公共资源访问,而根路径权限需要授权:

quarkus.http.auth.policy.user-policy.roles-allowed=user
quarkus.http.auth.permission.roles.paths=/api/*
quarkus.http.auth.permission.roles.policy=user-policy

quarkus.http.auth.permission.public.paths=/api/noauth/*
quarkus.http.auth.permission.public.policy=permit

匹配多条路径:最具体的方法获胜

如果一个路径被注册了多个权限集,那么任何指定了HTTP方法的权限集将被优先考虑,没有指定方法的权限集将不被考虑 (当然, 假设方法匹配的话)。在这种情况下,没有指定方法的权限集只有在请求方法与任何有方法权限的权限集不匹配时才会生效。

quarkus.http.auth.permission.permit1.paths=/public/*
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD

quarkus.http.auth.permission.deny1.paths=/public/*
quarkus.http.auth.permission.deny1.policy=deny
Given the above permission set, GET /public/foo would match both permission sets' paths, but because it matches the permit1 permission set’s explicit method, permit1 will be chosen and the request will be accepted. PUT /public/foo on the other hand, will not match the method permissions of permit1 and so deny1 will be activated and reject the request.

匹配多条路径和方法:双赢

如果多个权限组指定了相同的路径和方法(或者多个权限组没有指定方法), 那么这两个权限都必须允许访问才能使请求被处理。请注意,要做到这一点,两个权限要么都必须指定方法,要么都不指定方法。这里就如之前所述那样,有具体的方法匹配会优先:

quarkus.http.auth.policy.user-policy1.roles-allowed=user
quarkus.http.auth.policy.admin-policy1.roles-allowed=admin

quarkus.http.auth.permission.roles1.paths=/api/*,/restricted/*
quarkus.http.auth.permission.roles1.policy=user-policy1

quarkus.http.auth.permission.roles2.paths=/api/*,/admin/*
quarkus.http.auth.permission.roles2.policy=admin-policy1
Given the above permission set, GET /api/foo would match both permission sets' paths, so would require both the user and admin roles.

拒绝访问的配置属性

有三个配置项可以改变RBAC拒绝行为:

quarkus.security.jaxrs.deny-unannotated-endpoints=true|false

如果设为 true,默认情况下将拒绝所有 JAX-RS 节点的访问,所以如果一个 JAX-RS 节点没有任何安全注解,那么它将默认为 @DenyAll 注解。这对于确保您不会意外地暴露出一个本应是安全的节点很有用。默认为 false

quarkus.security.jaxrs.default-roles-allowed=role1,role2

定义了未加注解节点的默认角色要求。角色'**'是一个特殊的角色,意味着任何认证的用户。该项不能与 deny-unannotated-endpoints 混合使用,因为deny会代替它生效。

quarkus.security.deny-unannotated-members=true|false
  • 如果设置为 "true",所有没有加安全注解但被定义在包含安全注解的方法的类中的CDI方法和JAX-RS节点的访问将被拒绝。默认为 false

禁用权限

权限可以在构建时通过每个声明权限的 enabled 属性来禁用,例如:

quarkus.http.auth.permission.permit1.enabled=false
quarkus.http.auth.permission.permit1.paths=/public/*,/css/*,/js/*,/robots.txt
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD

并在运行时通过系统属性或环境变量启用,例如: -Dquarkus.http.auth.permission.permit1.enabled=true

权限路径和http根路径

quarkus.http.root-path 配置项被用于改变 http节点上下文路径

默认情况下, quarkus.http.root-path 会自动被加在配置的权限路径之前并且不需要指定一个前置的斜杠,例如:

quarkus.http.auth.permission.permit1.paths=public/*,css/*,js/*,robots.txt

该配置相当于以下内容:

quarkus.http.auth.permission.permit1.paths=${quarkus.http.root-path}/public/*,${quarkus.http.root-path}/css/*,${quarkus.http.root-path}/js/*,${quarkus.http.root-path}/robots.txt

所有前置的斜杠将改变配置的权限路径的解释方式。配置的URL将按原样使用,并且如果 quarkus.http.root-path 的值改变后路径将不会被调整。例如:

quarkus.http.auth.permission.permit1.paths=/public/*,css/*,js/*,robots.txt

此配置将只影响从固定/静态URL /public 提供的资源,如果 quarkus.http.root-path 被设置为其他的 / ,那么它可能与您的应用程序资源不匹配。

更多信息见 Quarkus中的路径解析

使用注解的授权

Quarkus 具有内置的安全性,允许在 REST 节点和 CDI Bean 上使用基于通用安全注解 @RolesAllowed , @DenyAll , @PermitAll 的基于角色的访问控制 (RBAC )。在 [subject-example] 中给出了一个同时使用 JAX-RS 和通用安全注解来描述和保护其节点的例子。Quarkus 还提供了 io.quarkus.security.Authenticated 注解,它将允许任何经过认证的用户访问该资源(相当于 @RolesAllowed("**") )。

SubjectExposingResource Example
import java.security.Principal;

import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

@Path("subject")
public class SubjectExposingResource {

    @GET
    @Path("secured")
    @RolesAllowed("Tester") (1)
    public String getSubjectSecured(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal(); (2)
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("unsecured")
    @PermitAll (3)
    public String getSubjectUnsecured(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal(); (4)
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("denied")
    @DenyAll (5)
    public String getSubjectDenied(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal();
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }
}
1 /subject/secured 节点需要一个经过认证的用户,该用户通过使用 @RolesAllowed("Tester") 注解被授予 "Tester "的角色。
2 该节点通过 JAX-RS SecurityContext 获取用户principal。对于受保护节点它将永不为null。
3 /subject/unsecured 节点通过使用 @PermitAll 注解来允许未认证的访问。
4 如果调用者未认证,这个获取用户principal的调用会返回null, 否则不会为null。
5 /subject/denied 节点使用了 @DenyAll 注解,所以不允许任何访问,即使访问是认证过的。

参考文献