Quarkus Spring Security API 扩展
虽然鼓励用户使用 Java 标准注解进行安全授权,但 Quarkus 以 spring-security
扩展的形式为 Spring Security 提供了兼容层。
本指南介绍了 Quarkus 应用程序如何利用广为人知的 Spring Security 注解,在 RESTful 服务上使用角色定义授权。
先决条件
要完成本指南,您需要
-
大约 15 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
如果您想使用它,可以选择 Quarkus CLI
-
如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置
-
需要对 Spring Web 扩展有一定的了解
解决方案
我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。
克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或者下载一个 归档文件。
解决方案位于 spring-security-quickstart
目录中。
创建 Maven 项目
首先,我们需要一个新项目。使用以下命令创建一个新项目
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数用双引号括起来,例如"-DprojectArtifactId=spring-security-quickstart"
此命令生成一个项目,该项目导入 spring-web
、spring-security
和 security-properties-file
扩展。
如果已经配置了 Quarkus 项目,可以通过在项目根目录中运行以下命令,将 spring-web
、spring-security
和 security-properties-file
扩展添加到项目中
quarkus extension add spring-web,spring-security,quarkus-elytron-security-properties-file,rest-jackson
./mvnw quarkus:add-extension -Dextensions='spring-web,spring-security,quarkus-elytron-security-properties-file,rest-jackson'
./gradlew addExtension --extensions='spring-web,spring-security,quarkus-elytron-security-properties-file,rest-jackson'
这会将以下内容添加到您的构建文件中
<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"));
}
}
打包并运行应用程序
使用以下命令运行应用程序
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
在浏览器中打开 https://:8080/greeting。
结果应该是: {"message": "hello"}
。
修改控制器以保护 hello
方法
为了限制对具有特定角色的用户的 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
中定义用户和角色。有关此扩展的更多信息,请查看相关指南。一个示例配置如下
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"));
}
}
测试更改
手动
- 允许访问
-
再次在浏览器中打开 https://:8080/greeting 并在显示的对话框中输入
scott
和jb0ss
。应该显示单词
hello
。 - 禁止访问
-
再次在浏览器中打开 https://:8080/greeting 并让显示的对话框为空。
结果应该是
Access to localhost was denied You don't have authorization to view this page. HTTP ERROR 403
一些浏览器会保存基本身份验证的凭据。如果未显示对话框,请尝试清除保存的登录名或使用隐私模式 |
支持的 Spring Security 注解
Quarkus 目前仅支持 Spring Security 提供的一部分功能,并且计划提供更多功能。更具体地说,Quarkus 支持基于角色的授权语义的安全相关功能(想想 @Secured
而不是 @RolesAllowed
)。
注解
下表总结了支持的注解
名称 | 注释 | Spring 文档 |
---|---|---|
@Secured |
参见上文 |
|
@PreAuthorize |
有关更多详细信息,请参见下一节 |
@PreAuthorize
Quarkus 提供对 Spring Security 的 @PreAuthorize
注解的一些最常用功能的支持。支持的表达式如下
- hasRole
-
要测试当前用户是否具有特定角色,可以在
@PreAuthorize
中使用hasRole
表达式。一些示例为:
@PreAuthorize("hasRole('admin')")
,@PreAuthorize("hasRole(@roles.USER)")
,其中roles
是一个可以像这样定义的 beanimport org.springframework.stereotype.Component; @Component public class Roles { public final String ADMIN = "admin"; public final String USER = "user"; }
- hasAnyRole
-
与
hasRole
类似,用户可以使用hasAnyRole
来检查登录用户是否具有任何指定的角色。一些示例为:
@PreAuthorize("hasAnyRole('admin')")
,@PreAuthorize("hasAnyRole(@roles.USER, 'view')")
- permitAll
-
将
@PreAuthorize("permitAll()")
添加到方法将确保任何用户(包括匿名用户)都可以访问该方法。将其添加到类将确保该类的所有未用任何其他 Spring Security 注解注释的公共方法都可以访问。 - denyAll
-
将
@PreAuthorize("denyAll()")
添加到方法将确保任何用户都无法访问该方法。将其添加到类将确保该类的所有未用任何其他 Spring Security 注解注释的公共方法对任何用户都不可访问。 - isAnonymous
-
当用
@PreAuthorize("isAnonymous()")
注释 bean 方法时,只有当前用户是匿名用户(即未登录用户)时,该方法才可访问。 - isAuthenticated
-
当用
@PreAuthorize("isAuthenticated()")
注释 bean 方法时,只有当前用户是登录用户时,该方法才可访问。本质上,该方法仅对匿名用户不可用。 - #paramName == authentication.principal.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
使用 authentication.
是可选的,因此使用principal.username
具有相同的结果。 - #paramName != authentication.principal.username
-
这与前面的表达式类似,不同之处在于方法参数必须与登录用户名不同。
- @beanName.method()
-
此语法允许开发人员指定特定 bean 的方法执行将确定当前用户是否可以访问受保护的方法。
最好用一个例子来解释该语法。假设已经创建了一个
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
。
组合表达式
@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() {
}
目前,表达式不支持逻辑运算符的括号,并且从左到右进行评估 |
重要的技术说明
请注意,Quarkus 中的 Spring 支持不会启动 Spring 应用程序上下文,也不会运行任何 Spring 基础结构类。Spring 类和注解仅用于读取元数据和/或用作用户代码方法返回类型或参数类型。这对最终用户来说意味着添加任意 Spring 库不会有任何影响。此外,Spring 基础结构类(例如 org.springframework.beans.factory.config.BeanPostProcessor
)将不会执行。
转换表
下表显示了如何将 Spring Security 注解转换为 Jakarta REST 注解。
Spring | Jakarta REST | 注释 |
---|---|---|
@Secured("admin") |
@RolesAllowed("admin") |
|
@PreAuthorize |
没有直接替换 |
Quarkus 以不同的方式处理复杂的授权,有关详细信息,请参见本指南 |