自定义端点
对 Jmix 应用程序的请求受到 Spring Security 框架的保护。本章节介绍如何配置对自定义 API 端点的访问。
要点信息
要深入了解端点安全性的原理,请阅读 Spring Security 文档的相关部分:
Spring Security 使用特殊的 SecurityFilterChain bean 来确定需要保护哪些 URL。每个 SecurityFilterChain
bean 都由 HttpSecurity builder 配置。一个应用程序可以声明多个 SecurityFilterChain
bean。此时,多个 bean 之间的顺序非常重要。请参阅 Spring Security 文档的 多 HttpSecurity 实例 部分,了解如何配置多个 HttpSecurity
对象。
默认情况下,每个 Jmix 应用程序都包含一个安全配置,该配置扩展了 VaadinWebSecurity 类。该配置设置对 Vaadin 内部端点的访问,并将所有请求的授权委托给 Jmix 和 Vaadin 机制实现(使用视图控制器注解或分析用户的资源角色授予对视图的访问权限)。该配置创建的 SecurityFilterChain
具有最低优先级,并且永远在最后调用。
自定义端点安全性
如需为端点定义自定义的安全规则,需要声明一个新的 SecurityFilterChain
bean。重要的是,这个 bean 的顺序必须小于 Jmix 框架中 SecurityFilterChain
bean 的顺序。
Jmix 使用的顺序值常量在 JmixSecurityFilterChainOrder
接口中定义。经验规则是使用 JmixSecurityFilterChainOrder.CUSTOM
、JmixSecurityFilterChainOrder.CUSTOM - 10
之类的值作为自定义 chain 的顺序。
下面是一个简单的 SecurityFilterChain
bean 示例:
@Bean
@Order(JmixSecurityFilterChainOrder.CUSTOM)
SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/public/**")
.authorizeHttpRequests(authorize ->
authorize.anyRequest().permitAll()
);
return http.build();
}
上面的配置授予对 /public/**
匹配端点的所有请求的访问权限。
示例
公共端点
假设在一个控制器中有两个方法,这两个方法需要开放给任何用户,无需认证。
@RestController
public class GreetingController {
@GetMapping("/greeting/hello")
public String hello() {
return "Hello!";
}
@PostMapping("/greeting/hi")
public String hi() {
return "Hi!";
}
}
公共端点的访问可以用下列配置:
@Configuration
public class AnonymousControllerSecurityConfiguration {
@Bean
@Order(JmixSecurityFilterChainOrder.CUSTOM) (1)
SecurityFilterChain greetingFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/greeting/**") (2)
.authorizeHttpRequests(authorize ->
authorize.anyRequest().permitAll() (3)
)
.csrf(csrf -> csrf.disable()); (4)
JmixHttpSecurityUtils.configureAnonymous(http); (5)
return http.build();
}
}
1 | JmixSecurityFilterChainOrder.CUSTOM 的顺序值小于其他 Jmix chain 的顺序,因此会在其他 Jmix chain 之前使用。 |
2 | securityMatcher() 用于确定是否将 HttpSecurity 应用于请求。示例中的请求将匹配符合 /greeting/** 的 URL。对其他 URL 的请求将由 Jmix UI 模块的默认 chain 处理。 |
3 | permitAll() 指令授予访问权限。 |
4 | 为 POST 请求禁用 CSRF。 |
5 | 调用 JmixHttpSecurityUtils.configureAnonymous(http) 配置匿名认证,将 UserRepository 返回的匿名用户设置到安全上下文中。 |
HTTP 基本认证
示例演示如何使用 HTTP 基本认证 保护控制器的端点。
控制器类:
@RestController
@RequestMapping("/api")
public class BasicGreetingController {
@GetMapping("/hello")
public String hello() {
return "Hello!";
}
@GetMapping("/public/hi")
public String publicHi() {
return "Hi!";
}
}
所有能匹配 /api/**
URL 的请求需要使用 HTTP 基本认证进行保护,而能匹配 /api/public/**
的请求则对所有用户开放。这可以通过以下配置实现:
@Configuration
public class BasicControllerSecurityConfiguration {
@Bean
@Order(JmixSecurityFilterChainOrder.CUSTOM) (1)
SecurityFilterChain basicControllerFilterChain(
HttpSecurity http,
AuthenticationManager authenticationManager) throws Exception {
http.securityMatcher("/api/**") (2)
.authorizeHttpRequests(requests ->
requests
.requestMatchers("/api/public/**").permitAll() (3)
.anyRequest().authenticated() (4)
)
.httpBasic(Customizer.withDefaults()) (5)
.authenticationManager(authenticationManager); (6)
return http.build();
}
}
1 | JmixSecurityFilterChainOrder.CUSTOM 的顺序值小于其他 Jmix chain 的顺序,因此会在其他 Jmix chain 之前使用。 |
2 | HttpSecurity 仅用于 /api/** 的请求。 |
3 | 如果 matcher 匹配成功,则我们可以配置进一步的规则。所有 /api/public/** 的请求都无需认证。 |
4 | 所有不能匹配 /api/public/** 的请求都需要认证。 |
5 | 启用基本认证。 |
6 | 使用 Jmix 配置的 AuthenticationManager 做基本认证。 |
对 /api/**
端点的请求必须包含一个请求头 - Authorization: Basic <credentials>
,其中 <credentials>
是用户名和密码以冒号连接后的 Base64 编码。示例:
GET /api/hello HTTP/1.1
Host: server.example.com
Authorization: Basic YWRtaW46YWRtaW4=
在这个示例中,/api/public/** 的公开访问也可以通过另一个 SecurityFilterChain bean 配置,带有 securityMatcher("/api/public/**") 且顺序要小于当前的 bean,例如,JmixSecurityFilterChainOrder.CUSTOM - 10 。
|
基于 Token 的认证
可以使用由 授权服务 颁发的 token 来保护自定义端点。授权服务扩展组件的安全配置提供了专门的扩展点。
假设有以下 REST 控制器:
@RestController
public class GreetingController {
@GetMapping("/greeting/hello")
public String hello() {
return "Hello!";
}
@PostMapping("/greeting/hi")
public String hi() {
return "Hi!";
}
}
如需用 token 保护 /greeting/**
端点,需要定义一个实现 io.jmix.core.security.AuthorizedUrlsProvider
接口的 bean,并从 getAuthenticatedUrlPatterns()
方法返回 URL 模式列表:
@Component
public class GreetingAuthorizedUrlsProvider implements AuthorizedUrlsProvider {
@Override
public Collection<String> getAuthenticatedUrlPatterns() {
return List.of("/greeting/**");
}
@Override
public Collection<String> getAnonymousUrlPatterns() {
return List.of();
}
}
完成上述配置后,所有对 /greeting/**
端点的请求都需要在 Authorization
头中带 access token。例如:
GET /greeting/hello HTTP/1.1
Host: server.example.com
Authorization: Bearer <ACCESS_TOKEN>
问题排查
如果遇到 401 Unauthorized
、403 Forbidden HTTP
错误或任何其他与端点安全行相关的问题,那么很可能 Spring Security logging 会记录下一些蛛丝马迹。
要启用日志记录,可在 application.properties
配置以下应用程序属性值为 DEBUG 或 TRACE:
logging.level.org.springframework.security = TRACE