Quarkus Extension for Spring Security API
虽然鼓励用户使用 Java 标准注释来实现安全授权,但是 Quarkus 以 `spring-security`扩展的形式为 Spring Security 提供兼容层。 此指南解释了 Quarkus 应用程序如何利用著名的 Spring Security 注释,使用角色定义对 REST 服务的授权。
- Prerequisites
- Solution
- Creating the Maven project
- GreetingController
- GreetingControllerTest
- Package and run the application
- Modify the controller to secure the
hello
method - GreetingControllerTest
- Test the changes
- Supported Spring Security annotations
- Important Technical Note
- Conversion Table
- More Spring guides
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
克隆 Git 存储库: git clone $${quickstarts-base-url}.git
,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。
解决方案位于 spring-security-quickstart
directory中。
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令生成一个项目,该项目导入 spring-web
, `spring-security`和 `security-properties-file`扩展。
如果您已配置 Quarkus 项目,则可以通过在项目基础目录中运行以下命令来将 spring-web
, `spring-security`和 `security-properties-file`扩展添加到项目中:
quarkus extension add {add-extension-extensions}
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
./gradlew addExtension --extensions='{add-extension-extensions}'
这会将以下内容添加到构建文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-security</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-spring-web")
implementation("io.quarkus:quarkus-spring-security")
implementation("io.quarkus:quarkus-elytron-security-properties-file")
implementation("io.quarkus:quarkus-rest-jackson")
有关 `security-properties-file`的更多信息,您可以查看 quarkus-elytron-security-properties-file扩展指南。
GreetingController
Quarkus Maven 插件自动生成一个具有 Spring Web 注释的控制器来定义我们的 REST 端点(而不是默认使用的 Jakarta REST)。首先创建一个 src/main/java/org/acme/spring/security/GreetingController.java
,它是一个具有 Spring Web 注释的控制器,用于定义我们的 REST 端点,如下所示:
package org.acme.spring.security;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/greeting")
public class GreetingController {
@GetMapping
public String hello() {
return "Hello Spring";
}
}
GreetingControllerTest
请注意,还创建了该控制器的测试:
package org.acme.spring.security;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
class GreetingControllerTest {
@Test
void testHelloEndpoint() {
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("Hello Spring"));
}
}
Package and run the application
使用以下内容运行应用程序:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
用浏览器打开 [role="bare"][role="bare"]http://localhost:8080/greeting.
结果应为: {"message": "hello"}
.
Modify the controller to secure the hello
method
为了限制对具有某些角色的用户访问 hello
方法,将使用 @Secured
注解。更新后的控制器如下:
package org.acme.spring.security;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/greeting")
public class GreetingController {
@Secured("admin")
@GetMapping
public String hello() {
return "hello";
}
}
针对我们的示例设置用户和角色最简单的方法就是使用 security-properties-file
扩展。此扩展基本上允许在主 Quarkus 配置文件 - `application.properties`中定义用户和角色。有关此扩展的更多信息,请检查 the associated guide。示例配置如下:
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.scott=jb0ss
quarkus.security.users.embedded.roles.scott=admin,user
quarkus.security.users.embedded.users.stuart=test
quarkus.security.users.embedded.roles.stuart=user
请注意,测试也需要更新。它可能如下所示:
GreetingControllerTest
package org.acme.spring.security;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingControllerTest {
@Test
public void testHelloEndpointForbidden() {
given().auth().preemptive().basic("stuart", "test")
.when().get("/greeting")
.then()
.statusCode(403);
}
@Test
public void testHelloEndpoint() {
given().auth().preemptive().basic("scott", "jb0ss")
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("hello"));
}
}
Test the changes
Manually
- Access allowed
-
Open your browser again to [role="bare"]http://localhost:8080/greeting and introduce
scott
andjb0ss
in the dialog displayed.会显示单词
hello
。 - Access forbidden
-
Open your browser again to [role="bare"]http://localhost:8080/greeting and let empty the dialog displayed.
结果应该是:
Access to localhost was denied
You don't have authorization to view this page.
HTTP ERROR 403
一些浏览器会保存基本身份验证的凭证。如果没有显示该对话框,请尝试清除已保存的登录信息或使用私人模式。 |
Supported Spring Security annotations
Quarkus 目前只支持 Spring Security 提供的一部分功能,而且正在规划更多功能。具体而言,Quarkus 支持以下安全相关功能:基于角色的授权语义(不妨考虑使用 @Secured
,而不是 @RolesAllowed
)。
Annotations
下表总结了受支持的注释:
Name | Comments | Spring documentation |
---|---|---|
@Secured |
See secure |
|
@PreAuthorize |
有关更多详细信息,请参见下一部分。 |
@PreAuthorize
Quarkus 支持 Spring Security 的 @PreAuthorize
注解的一些最常用功能。支持的表达式如下所示:
- hasRole
-
要在
@PreAuthorize`中测试当前用户是否具有特定角色,可以使用 `hasRole
表达式。 一些示例包括:@PreAuthorize("hasRole('admin')")
、@PreAuthorize("hasRole(@roles.USER)")
,其中roles
是一个可能已如下定义的 Bean:
import org.springframework.stereotype.Component;
@Component
public class Roles {
public final String ADMIN = "admin";
public final String USER = "user";
}
- hasAnyRole
-
In the same fashion as
hasRole
, users can usehasAnyRole
to check if the logged-in user has any of the specified roles.一些示例包括:
@PreAuthorize("hasAnyRole('admin')")
、@PreAuthorize("hasAnyRole(@roles.USER, 'view')")
- permitAll
-
Adding
@PreAuthorize("permitAll()")
to a method will ensure that method is accessible by any user (including anonymous users). Adding it to a class will ensure that all public methods of the class that are not annotated with any other Spring Security annotation will be accessible. - denyAll
-
Adding
@PreAuthorize("denyAll()")
to a method will ensure that method is not accessible by any user. Adding it to a class will ensure that all public methods of the class that are not annotated with any other Spring Security annotation will not be accessible to any user. - isAnonymous
-
When annotating a bean method with
@PreAuthorize("isAnonymous()")
the method will only be accessible if the current user is anonymous - i.e. a non logged-in user. - isAuthenticated
-
When annotating a bean method with
@PreAuthorize("isAuthenticated()")
the method will only be accessible if the current user is a logged-in user. Essentially the method is only unavailable for anonymous users. - #paramName == authentication.principal.username
-
This syntax allows users to check if a parameter (or a field of the parameter) of the secured method is equal to the logged-in username.
此用例的一些示例包括:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
// this syntax requires getters for field access
public String getName() {
return name;
}
}
@Component
public class MyComponent {
@PreAuthorize("#username == authentication.principal.username") 1
public void doSomething(String username, String other){
}
@PreAuthorize("#person.name == authentication.principal.username") 2
public void doSomethingElse(Person person){
}
}
1 | 如果当前登录的用户与 username 方法参数相同,则可以执行 doSomething 。 |
2 | 如果当前登录的用户与 person 方法参数的 name 域相同,则可以执行 doSomethingElse 。 |
使用 |
- #paramName != authentication.principal.username
-
This is similar to the previous expression with the difference being that the method parameter must be different from the logged-in username.
- @beanName.method()
-
This syntax allows developers to specify that the execution of method of a specific bean will determine if the current user can access the secured method.
语法最好通过示例来解释。让我们假设
MyComponent
Bean 是这样创建的:
@Component
public class MyComponent {
@PreAuthorize("@personChecker.check(#person, authentication.principal.username)")
public void doSomething(Person person){
}
}
doSomething
方法已使用 @PreAuthorize
进行注释,该注释中包含一个表达式,指明需要调用名称为 personChecker
的 Bean 的方法 check
以确定当前用户是否有权调用 doSomething
方法。
PersonChecker
的示例可能是:
@Component
public class PersonChecker {
public boolean check(Person person, String username) {
return person.getName().equals(username);
}
}
请注意,对于 check
方法,参数类型必须与 @PreAuthorize
中指定的类型匹配,并且返回类型必须是 boolean
。
Combining expressions
@PreAuthorize
注释允许使用逻辑 AND
/ OR
组合表达式。目前,有一个限制,即只能使用单个逻辑运算(这意味着不允许将 AND
和 OR
混合)。
一些允许的表达式的示例如下:
@PreAuthorize("hasAnyRole('user', 'admin') AND #user == principal.username")
public void allowedForUser(String user) {
}
@PreAuthorize("hasRole('user') OR hasRole('admin')")
public void allowedForUserOrAdmin() {
}
@PreAuthorize("hasAnyRole('view1', 'view2') OR isAnonymous() OR hasRole('test')")
public void allowedForAdminOrAnonymous() {
}
目前,表达式不支持逻辑运算符的括号,并且从左到右进行求值。
Important Technical Note
请注意,Quarkus 中的 Spring 支持不会启动 Spring 应用程序上下文,也不会运行任何 Spring 基础设施类。Spring 类和注释仅用于读取元数据和/或用作用户代码方法返回类型或参数类型。这对最终用户意味着,添加任意 Spring 库不会产生任何影响。此外,Spring 基础设施类(例如 org.springframework.beans.factory.config.BeanPostProcessor
)不会被执行。
Conversion Table
下表显示了如何将 Spring Security 注释转换为 Jakarta REST 注释。
Spring | Jakarta REST | Comments |
---|---|---|
@Secured("admin") |
@RolesAllowed("admin") |
|
@PreAuthorize |
No direct replacement |
Quarkus 以不同的方式处理复杂授权,有关详细信息,请参阅 this guide。 |
More Spring guides
Quarkus 还有更多 Spring 兼容性功能。请参阅以下指南以获取更多详情: