Quarkus Extension for Spring Security API
虽然鼓励用户使用 Java 标准注释来实现安全授权,但是 Quarkus 以 `spring-security`扩展的形式为 Spring Security 提供兼容层。
While users are encouraged to use Java standard annotations for security authorizations, Quarkus provides a compatibility layer for Spring Security in the form of the spring-security
extension.
此指南解释了 Quarkus 应用程序如何利用著名的 Spring Security 注释,使用角色定义对 REST 服务的授权。
This guide explains how a Quarkus application can leverage the well-known Spring Security annotations to define authorizations on RESTful services using roles.
Prerequisites
include::{includes}/prerequisites.adoc[]* 熟悉 Spring Web 扩展
Unresolved directive in spring-security.adoc - include::{includes}/prerequisites.adoc[] * Some familiarity with the Spring Web extension
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
克隆 Git 存储库: git clone {quickstarts-clone-url}
,或下载 {quickstarts-archive-url}[存档]。
Clone the Git repository: git clone {quickstarts-clone-url}
, or download an {quickstarts-archive-url}[archive].
解决方案位于 spring-security-quickstart
directory中。
The solution is located in the spring-security-quickstart
directory.
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
First, we need a new project. Create a new project with the following command:
Unresolved directive in spring-security.adoc - include::{includes}/devtools/create-app.adoc[]
此命令生成一个项目,该项目导入 spring-web
, `spring-security`和 `security-properties-file`扩展。
This command generates a project which imports the spring-web
, spring-security
and security-properties-file
extensions.
如果您已配置 Quarkus 项目,则可以通过在项目基础目录中运行以下命令来将 spring-web
, `spring-security`和 `security-properties-file`扩展添加到项目中:
If you already have your Quarkus project configured, you can add the spring-web
, spring-security
and security-properties-file
extensions
to your project by running the following command in your project base directory:
Unresolved directive in spring-security.adoc - include::{includes}/devtools/extension-add.adoc[]
这会将以下内容添加到构建文件中:
This will add the following to your build file:
<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扩展指南。
For more information about security-properties-file
, you can check out the guide of the quarkus-elytron-security-properties-file extension.
GreetingController
Quarkus Maven 插件自动生成一个具有 Spring Web 注释的控制器来定义我们的 REST 端点(而不是默认使用的 Jakarta REST)。首先创建一个 src/main/java/org/acme/spring/security/GreetingController.java
,它是一个具有 Spring Web 注释的控制器,用于定义我们的 REST 端点,如下所示:
The Quarkus Maven plugin automatically generated a controller with the Spring Web annotations to define our REST endpoint (instead of the Jakarta REST ones used by default).
First create a src/main/java/org/acme/spring/security/GreetingController.java
, a controller with the Spring Web annotations to define our REST endpoint, as follows:
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
请注意,还创建了该控制器的测试:
Note that a test for the controller has been created as well:
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
使用以下内容运行应用程序:
Run the application with:
Unresolved directive in spring-security.adoc - include::{includes}/devtools/dev.adoc[]
用浏览器打开 [role="bare"][role="bare"]http://localhost:8080/greeting.
Open your browser to [role="bare"]http://localhost:8080/greeting.
结果应为: {"message": "hello"}
.
The result should be: {"message": "hello"}
.
Modify the controller to secure the hello
method
为了限制对具有某些角色的用户访问 hello
方法,将使用 @Secured
注解。更新后的控制器如下:
In order to restrict access to the hello
method to users with certain roles, the @Secured
annotation will be utilized.
The updated controller will be:
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。示例配置如下:
The easiest way to set up users and roles for our example is to use the security-properties-file
extension. This extension essentially allows users and roles to be defined in the main Quarkus configuration file - application.properties
.
For more information about this extension check the associated guide.
An example configuration would be the following:
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
请注意,测试也需要更新。它可能如下所示:
Note that the test also needs to be updated. It could look like:
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
Automatically
在开发模式下按 r
,或使用以下命令运行此应用程序:
Press r
, while in dev mode, or run the application with:
Unresolved directive in spring-security.adoc - include::{includes}/devtools/test.adoc[]
所有测试都应该成功。
All tests should succeed.
Manually
- Access allowed
-
Open your browser again to [role="bare"]http://localhost:8080/greeting and introduce
scott
andjb0ss
in the dialog displayed.会显示单词
hello
。
The word hello
should be displayed.
- Access forbidden
-
Open your browser again to [role="bare"]http://localhost:8080/greeting and let empty the dialog displayed.
结果应该是:
The result should be:
Access to localhost was denied
You don't have authorization to view this page.
HTTP ERROR 403
一些浏览器会保存基本身份验证的凭证。如果没有显示该对话框,请尝试清除已保存的登录信息或使用私人模式。 Some browsers save credentials for basic authentication. If the dialog is not displayed, try to clear saved logins or use the Private mode |
Supported Spring Security annotations
Quarkus 目前只支持 Spring Security 提供的一部分功能,而且正在规划更多功能。具体而言,Quarkus 支持以下安全相关功能:基于角色的授权语义(不妨考虑使用 @Secured
,而不是 @RolesAllowed
)。
Quarkus currently only supports a subset of the functionality that Spring Security provides with more features being planned. More specifically, Quarkus supports the security related features of role-based authorization semantics
(think of @Secured
instead of @RolesAllowed
).
Annotations
下表总结了受支持的注释:
The table below summarizes the supported annotations:
Name | Comments | Spring documentation |
---|---|---|
@Secured |
See secure |
|
@PreAuthorize |
See next section for more details |
@PreAuthorize
Quarkus 支持 Spring Security 的 @PreAuthorize
注解的一些最常用功能。支持的表达式如下所示:
Quarkus provides support for some of the most used features of Spring Security’s @PreAuthorize
annotation.
The expressions that are supported are the following:
- hasRole
-
要在
@PreAuthorize`中测试当前用户是否具有特定角色,可以使用 `hasRole
表达式。
To test if the current user has a specific role, the hasRole
expression can be used inside @PreAuthorize
.
一些示例包括: @PreAuthorize("hasRole('admin')")
、 @PreAuthorize("hasRole(@roles.USER)")
,其中 roles
是一个可能已如下定义的 Bean:
Some examples are: @PreAuthorize("hasRole('admin')")
, @PreAuthorize("hasRole(@roles.USER)")
where the roles
is a bean that could be defined like so:
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')")
Some examples are: @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.
此用例的一些示例包括:
Examples of this use case are:
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 | doSomething can be executed if the current logged-in user is the same as the username method parameter |
2 | doSomethingElse can be executed if the current logged-in user is the same as the name field of person method parameter |
使用 |
the use of |
- #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 是这样创建的:
The syntax is best explained with an example.
Let’s assume that a MyComponent
bean has been created like so:
@Component
public class MyComponent {
@PreAuthorize("@personChecker.check(#person, authentication.principal.username)")
public void doSomething(Person person){
}
}
doSomething
方法已使用 @PreAuthorize
进行注释,该注释中包含一个表达式,指明需要调用名称为 personChecker
的 Bean 的方法 check
以确定当前用户是否有权调用 doSomething
方法。
The doSomething
method has been annotated with @PreAuthorize
using an expression that indicates that method check
of a bean named personChecker
needs
to be invoked to determine whether the current user is authorized to invoke the doSomething
method.
PersonChecker
的示例可能是:
An example of the PersonChecker
could be:
@Component
public class PersonChecker {
public boolean check(Person person, String username) {
return person.getName().equals(username);
}
}
请注意,对于 check
方法,参数类型必须与 @PreAuthorize
中指定的类型匹配,并且返回类型必须是 boolean
。
Note that for the check
method the parameter types must match what is specified in @PreAuthorize
and that the return type must be a boolean
.
Combining expressions
@PreAuthorize
注释允许使用逻辑 AND
/ OR
组合表达式。目前,有一个限制,即只能使用单个逻辑运算(这意味着不允许将 AND
和 OR
混合)。
The @PreAuthorize
annotations allows for the combination of expressions using logical AND
/ OR
. Currently, there is a limitation where only a single
logical operation can be used (meaning mixing AND
and OR
isn’t allowed).
一些允许的表达式的示例如下:
Some examples of allowed expressions are:
@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() {
}
目前,表达式不支持逻辑运算符的括号,并且从左到右进行求值。
Currently, expressions do not support parentheses for logical operators and are evaluated from left to right
Important Technical Note
请注意,Quarkus 中的 Spring 支持不会启动 Spring 应用程序上下文,也不会运行任何 Spring 基础设施类。Spring 类和注释仅用于读取元数据和/或用作用户代码方法返回类型或参数类型。这对最终用户意味着,添加任意 Spring 库不会产生任何影响。此外,Spring 基础设施类(例如 org.springframework.beans.factory.config.BeanPostProcessor
)不会被执行。
Please note that the Spring support in Quarkus does not start a Spring Application Context nor are any Spring infrastructure classes run.
Spring classes and annotations are only used for reading metadata and / or are used as user code method return types or parameter types.
What that means for end users, is that adding arbitrary Spring libraries will not have any effect. Moreover, Spring infrastructure
classes (like org.springframework.beans.factory.config.BeanPostProcessor
for example) will not be executed.
Conversion Table
下表显示了如何将 Spring Security 注释转换为 Jakarta REST 注释。
The following table shows how Spring Security annotations can be converted to Jakarta REST annotations.
Spring | Jakarta REST | Comments |
---|---|---|
@Secured("admin") |
@RolesAllowed("admin") |
|
@PreAuthorize |
No direct replacement |
Quarkus handles complex authorisation differently, see this guide for details |
More Spring guides
Quarkus 还有更多 Spring 兼容性功能。请参阅以下指南以获取更多详情:
Quarkus has more Spring compatibility features. See the following guides for more details: