Security Testing

Configuring User Information

您可以使用 quarkus-elytron-security-properties-file 来测试安全。这同时支持在 application.properties 和独立属性文件中嵌入用户信息。

例如,以下配置将允许在生产中配置用户(在生产中需要 OAuth2)和使用 Configuration Profiles 的开发模式中的用户。

# Configure embedded authentication
%dev.quarkus.security.users.embedded.enabled=true
%dev.quarkus.security.users.embedded.plain-text=true
%dev.quarkus.security.users.embedded.users.scott=reader
%dev.quarkus.security.users.embedded.users.stuart=writer
%dev.quarkus.security.users.embedded.roles.scott=READER
%dev.quarkus.security.users.embedded.roles.stuart=READER,WRITER

# Configure OAuth2
quarkus.oauth2.enabled=true
%dev.quarkus.oauth2.enabled=false
quarkus.oauth2.client-id=client-id
quarkus.oauth2.client-secret=client-secret
quarkus.oauth2.introspection-url=http://host:port/introspect

Test Security Extension

Quarkus 为使用不同用户以及在禁用安全子系统的情况下进行测试提供了显式支持。要使用此功能,您必须包含 quarkus-test-security 依赖项:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-security</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-test-security")

此工件提供了 io.quarkus.test.security.TestSecurity 注释,该注释可以应用于测试方法和测试类以控制测试运行的安全上下文。这允许您执行两件事,您可以禁用授权,以便测试可以访问安全端点而无需进行身份验证,并且您可以指定希望测试在哪个标识下运行。

在禁用授权的情况下运行的测试可以将 enabled 属性设置为 false:

@Test
@TestSecurity(authorizationEnabled = false)
void someTestMethod() {
...
}

这将禁用所有访问检查,这允许测试访问安全端点而无需进行身份验证。

您还可以使用它来配置测试将作为其运行的当前用户:

@Test
@TestSecurity(user = "testUser", roles = {"admin", "user"})
void someTestMethod() {
...
}

这将使用具有给定用户名和角色的标识运行测试。请注意,这些可以组合起来,因此如果您在运行测试的同时禁用授权,这将很有用(如果端点需要存在标识,这可能很有用)。

有关测试依赖于注入的 JsonWebToken 的端点代码的更多详细信息,请参见 OpenID Connect Bearer Token Integration testingOpenID Connect Authorization Code Flow Integration testingSmallRye JWT Integration testing

此外,您可以为标识指定属性,可能是使用标识增强添加的自定义项:

@Inject
SecurityIdentity identity;

@Test
@TestSecurity(user = "testUser", "roles = {"admin, "user"}, attributes = {
            @SecurityAttribute(key = "answer", value = "42", type = AttributeType.LONG) }
void someTestMethod() {
    Long answer = identity.<Long>getAttribute("answer");
...
}

这将在类型为 Long 且名为 answer 的标识下运行测试。

此功能仅适用于 @QuarkusTest 并且将在 @QuarkusIntegrationTest 上*not* 工作。

@TestSecurity 也可用于元注释,例如类似如此:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    @TestSecurity(user = "testUser", roles = {"admin", "user"})
    public @interface TestSecurityMetaAnnotation {

    }

如果需要在多个测试方法中使用同一组安全设置,这会尤其有用。

Mixing security tests

如果使用 @TestSecurity 和基本的 Auth(这是在未定义任何内容时,成为后备的 authmechanism)测试安全功能变得有必要,则需要显式启用基本的 Auth,例如通过设置 quarkus.http.auth.basic=true%test.quarkus.http.auth.basic=true

同样,如果在使用 @TestSecurity 和 Bearer 令牌验证两者的情况下,需要测试安全功能,可以像下面的示例中那样同时利用两者:

@Test
@TestSecurity(user = "Bob")
public void testSecurityMetaAnnotation {
    RestAssured.given()
            .auth().oauth2(getTokenForUser("Alice")) 1
            .get("hello")
            .then()
            .statusCode(200)
            .body(Matchers.is("Hello Alice"));
    RestAssured.given()
            .get("hello")
            .then()
            .statusCode(200)
            .body(Matchers.is("Hello Bob")); 2
}

@Path("hello")
public static class HelloResource {

  @Inject
  SecurityIdentity identity;

  @Authenticated
  @GET
  public String sayHello() {
       return "Hello " + identity.getPrincipal().getName();
  }
}
1 使用 Bearer 令牌验证机制,因为 Bearer 访问令牌随 HTTP 请求发送。
2 没有授权头,因此测试安全扩展会创建用户 Bob

Path-based authentication

可以在启用基于路径的验证时,使用 authentication mechanisms must be combined,如示例所示,说明如何选择验证机制。

@Test
@TestSecurity(user = "testUser", roles = {"admin", "user"}, authMechanism = "basic") 1
void basicTestMethod() {
    ...
}

@Test
@TestSecurity(user = "testUser", roles = {"admin", "user"}, authMechanism = "form") 2
void formTestMethod() {
    ...
}
1 “authMechanism”属性选择基本验证。
2 “authMechanism”属性选择基于表单的验证。

在 Quarkus 应用程序中,可以使用注释为各个 Jakarta REST 端点选择一种特定的验证机制:

package org.acme.security.testing;

import io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication;
import io.quarkus.vertx.http.runtime.security.annotation.FormAuthentication;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/")
public class TestSecurityResource {

    @BasicAuthentication 1
    @GET
    @Path("basic-only")
    public String basicOnly() {
        return "basic-only";
    }

    @FormAuthentication 2
    @GET
    @Path("form-only")
    public String formOnly() {
        return "form-only";
    }
}
1 对来自 basicTestMethod 测试的 /basic-only 路径的所有 HTTP 请求都可以成功验证。
2 当从 formTestMethod 测试调用时,因为需要进行基本验证,相同的 HTTP 请求将失败。

反之,可以使用 HTTP 安全策略来选择特定路径的验证机制:

# require basic authentication for the '/basic-only' path
quarkus.http.auth.permission.basic.paths=/basic-only
quarkus.http.auth.permission.basic.policy=authenticated
quarkus.http.auth.permission.basic.auth-mechanism=basic

# require form-based authentication for the '/form-only' path
quarkus.http.auth.permission.form.paths=/form-only
quarkus.http.auth.permission.form.policy=authenticated
quarkus.http.auth.permission.form.auth-mechanism=form

Use Wiremock for Integration Testing

还可以使用 Wiremock 来模拟授权 OAuth2 和 OIDC 服务:有关更详细信息,请参阅 OAuth2 Integration testingOpenID Connect Bearer Token Integration testingOpenID Connect Authorization Code Flow Integration testingSmallRye JWT Integration testing