Quarkus - 安全指南

你可以使用注解及配置来定义 Quarkus 安全代码的安全授权要求,并可使用几种方式让 Quarkus 扩展加载安全认证信息。

认证源

Quarkus 支持多种认证信息源加载。您需要至少导入以下扩展之一,以使 Quarkus 知道如何查找验证信息以检查授权:

Table 1. 安全扩展
扩展(Extension) 描述

quarkus-elytron-security-properties-file

测试性的简单属性文件支持。将用户信息嵌入 application.properties 或者到独立 properties 文件中。

quarkus-security-jpa

支持通过JPA进行身份验证。

quarkus-elytron-security-jdbc

支持通过JDBC进行身份验证。

quarkus-elytron-security-oauth2

通过 Elytron 支持 OAuth2 flows 。此扩展可能很快就会被弃用,并被一个反应式 Vert.x 版本取代。

quarkus-smallrye-jwt

MicroProfile JWT实现,为使用Json Web令牌进行身份验证提供支持。 这还允许您根据 MP JWT 规范将 token 及 claims 注入到应用程序中。

quarkus-oidc

支持通过 OpenID Connect 提供程序(例如Keycloak)进行身份验证。

quarkus-keycloak-authorization

支持 Keycloak授权服务为策略执行者。

如何设置各种扩展功能的详细信息,请参见上述链接文档

通过HTTP进行身份验证

Quarkus has two built in authentication mechanisms for HTTP based FORM and BASIC auth. This mechanism is pluggable however so extensions can add additional mechanisms (most notably OpenID Connect for Keycloak based auth).

Quarkus具有两种基于 HTTP 的内置身份验证机制 FORM 和 BASIC 身份验证。 此机制是可插入的,因此可以结合其他机制(最显着的是基于Keycloak的身份验证的OpenID Connect)。

Basic 验证

设置 quarkus.http.auth.basic=true 来启用 Basic 身份验证 。 必须已经安装至少一个支持用户名/密码的 IdentityProvider 扩展,比如 Elytron JDBC

Form 验证

Quarkus 提供了基于 form 的身份验证,其工作方式与传统的 Servlet form 身份验证类似。 与传统的表单身份验证不同,由于 Quarkus 不提供群集的 HTTP 会话支持,因此身份验证的用户不存储在HTTP会话中。取而代之的是,身份验证信息存储在加密的 cookie 中,集群的所有成员都可以读取(如果它们都共享相同的加密密钥)。

可以使用 quarkus.http.auth.session.encryption-key 属性设置加密密钥,并且不能少于16个字符。 对该密钥使用 SHA-256 进行哈希处理,并将所得摘要(digest)用作 cookie 值的 AES-256 加密的密钥。 此 cookie 包含一个到期时间作为加密值的一部分,因此群集中的所有节点必须时间同步。 如果还在使用会话,则每隔一分钟会重新生成带新过期时间的新 cookie 。

以下属性可用于配置基于 form 的身份验证:

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

If form authentication is enabled

boolean

false

string

/login.html

string

/error.html

The landing page to redirect to if there is no saved page to redirect back to

string

/index.html

Option to disable redirect to landingPage if there is no saved page to redirect back to. Form Auth POST is followed by redirect to landingPage by default.

boolean

true

The inactivity (idle) timeout When inactivity timeout is reached, cookie is not renewed and a new login is enforced.

Duration

PT30M

How old a cookie can get before it will be replaced with a new cookie with an updated timeout, also referred to as "renewal-timeout". Note that smaller values will result in slightly more server load (as new encrypted cookies will be generated more often), however larger values affect the inactivity timeout as the timeout is set when a cookie is generated. For example if this is set to 10 minutes, and the inactivity timeout is 30m, if a users last request is when the cookie is 9m old then the actual timeout will happen 21m after the last request, as the timeout is only refreshed when a new cookie is generated. In other words no timeout is tracked on the server side; the timestamp is encoded and encrypted in the cookie itself and it is decrypted and parsed with each request.

Duration

PT1M

The cookie that is used to store the persistent session

string

quarkus-credential

About the Duration format

The format for durations uses the standard java.time.Duration format. You can learn more about it in the Duration#parse() javadoc.

You can also provide duration values starting with a number. In this case, if the value consists only of a number, the converter treats the value as seconds. Otherwise, PT is implicitly prepended to the value to obtain a standard java.time.Duration format.

在 REST 接口及 CDI Bean 用注解授权(Authorization)

Quarkus 内置有基于角色的访问控制(RBAC )通用注解 @RolesAllowed, @DenyAll, @PermitAll 可用于 REST 接口及 CDI beans。 SubjectExposingResource 例子 例子中演示了接口同时使用 JAX-RS 及通用安全注解。 Quarkus还提供了 io.quarkus.security.Authenticated 注解以允许任何已经通过身份验证的用户可以访问资源(相当于 @RolesAllowed("**") )。

SubjectExposingResource 例子
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 获取用户主体。对于受保护的接口,该字段将不会为空。
3 接口 /subject/unsecured@PermitAll 注解指明允许未经认证的用户也可访问。
4 如果调用方未通过身份验证,则此获取用户主体的调用将返回null;如果调用方已通过身份验证,则此调用将返回非null。
5 接口 /subject/denied@DenyAll 指明不允许任何人访问,无论是否通过认证。

Web 接口授权配置

Quarkus 集成了可插入Web安全层。如果启用了安全性,则所有HTTP请求都将执行权限检查,以确保允许它们继续执行。

在任何基于注释的授权检查之前,将执行配置认证检查,因此必须通过两项检查才能允许请求。

默认实现允许在 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 这定义了一个基于角色的策略,该策略允许具有user和admin角色的用户使用。这在后边规则中被引用
2 这是引用先前定义的策略的权限集。roles1 随便起的名字,您可以随意调用权限集。.
3 此权限引用默认的permit内置策略,以允许 GET 方法使用 /public 。在此示例中,这实际上是一个空操作,因为无论如何都会允许该请求。
4 此权限引用内置的内置deny策略/forbidden。这是精确的路径匹配,因为它不以*结尾。

权限是使用权限集在config中定义的。这些是任意命名的权限分组。每个权限集必须指定用于控制访问的策略。有三个内置的策略:deny, permitauthenticated,依次表示拒绝访问,允许访问和允许通过认证的用户访问。

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

Matching on paths, methods

Permission sets can also specify paths and methods as a comma separated list. If a path ends with '*' then it is considered to be a wildcard match and will match all sub paths, otherwise it is an exact match and will only match that specific path:

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

Matching path but not method

If a request would match one or more permission sets based on the path, but does not match any due to method requirements then the request is rejected.

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.

Matching multiple paths: longest wins

Matching is always done on a longest path basis, less specific permission sets are not considered if a more specific one has been matched:

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.

Matching multiple paths: most specific method wins

If a path is registered with multiple permission sets then any permission sets that specify a HTTP method will take precedence and permissions sets without a method will not be considered (assuming of course the method matches). In this instance, the permission sets without methods will only come into effect if the request method does not match any of the sets with method permissions.

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.

Matching multiple paths and methods: both win

If multiple permission sets specify the same path and method (or multiple have no method) then both permissions have to allow access for the request to proceed. Note that for this to happen both have to either have specified the method, or have no method, method specific matches take precedence as stated above:

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.

Authenticated representation

For every authenticated resource, you can inject a SecurityIdentity instance to get the authenticated identity information.

In some other contexts you may have other parallel representations of the same information (or parts of it) such as SecurityContext for JAX-RS or JsonWebToken for JWT.

Configuration

There are two configuration settings that alter the RBAC behavior:

  • quarkus.security.jaxrs.deny-unannotated-endpoints=true|false - if set to true, the access will be denied for all JAX-RS endpoints by default. That is if the security annotations do not define the access control. Defaults to false

  • quarkus.security.deny-unannotated-members=true|false - if set to true, the access will be denied to all CDI methods and JAX-RS endpoints that do not have security annotations but are defined in classes that contain methods with security annotations. Defaults to false.

Registering Security Providers

When running in native mode the default behavior for Graal native image generation is to only include the main "SUN" provider unless you have enabled SSL, in which case all security providers are registered. If you are not using SSL, then you can selectively register security providers by name using the quarkus.security.users.security-providers property. The following example illustrates configuration to register the "SunRsaSign" and "SunJCE" security providers:

Example Security Providers Configuration
quarkus.security.security-providers=SunRsaSign,SunJCE
...

Security Identity Customization

Internally, the identity providers create and update an istance of the io.quarkus.security.identity.SecurityIdentity class which holds the principal, roles, credentials which were used to authenticate the client (user) and other security attributes. An easy option to customize SecurityIdentity is to register a custom SecurityIdentityAugmentor, for example, the augmentor below adds an addition role:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.enterprise.context.ApplicationScoped;

import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {

    @Override
    public int priority() {
        return 0;
    }

    @Override
    public CompletionStage<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {

        CompletableFuture<SecurityIdentity> cs = new CompletableFuture<>();
        if (identity.isAnonymous()) {
            cs.complete(identity);
        } else {
            // create a new builder and copy principal, attributes, credentials and roles from the original
            QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder()
                .setPrincipal(identity.getPrincipal())
                .addAttributes(identity.getAttributes())
                .addCredentials(identity.getCredentials())
                .addRoles(identity.getRoles());

            // add custom role source here
            builder.addRole("dummy");

            cs.complete(builder.build());
        }

        return cs;
    }
}

Reactive Security

If you are going to use security in a reactive environment, you will likely need SmallRye Context Propagation:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>

This will allow you to propagate the identity throughout the reactive callbacks. You also need to make sure you are using an executor that is capable of propagating the identity (e.g. no CompletableFuture.supplyAsync), to make sure that quarkus can propagate it. For more information see the Context Propagation Guide.

quarkus.pro 是基于 quarkus.io 的非官方中文翻译站 ,最后更新 2020/04 。
沪ICP备19006215号-8
QQ交流群:1055930959
微信群: