访问控制
如需访问受保护的资源(REST API 端点),客户端必须提供有效访问 token。
获取通用 REST API token 的操作由认证服务(Authorization Server)扩展组件实现。当使用 Studio 添加 REST API 扩展时,会自动包含认证服务的 starter(io.jmix.authserver:jmix-authserver-starter
)。认证服务扩展组件基于 Spring Authorization Server 框架开发。
OAuth 2.1 协议指定了获取 token 的几种方式,称为授权类型(grant type)。下面我们将说明如何使用客户端凭证(Client Credential)和认证码(Authentication Code)进行授权。
客户端凭证授权
Client Credentials grant(客户端凭证授权) 支持在请求中使用注册客户端的凭证从认证服务获取访问 token。这种授权类型应当用于服务与服务的通信。
方案如下:
-
客户端使用 id 和秘钥在 Jmix 应用程序中注册。
-
为客户端分配资源和行级角色。
-
客户端应用程序,使用客户端 id 和秘钥获取访问 token。
-
客户端应用程序使用 token 访问受保护的 API 端点,数据权限受所分配的角色控制。
在 Getting Started 部分有一个示例,演示了如何在 application.properties
中注册客户端,并使用客户端凭证获取访问 token 授权。
除了在 application.properties
文件设置客户端属性外,也可以通过提供一个 RegisteredClientRepository
bean 进行注册。参考 Spring Authorization Server 文档 了解详情。
如果创建了一个 RegisteredClientRepository ,则 application.properties 的配置将不会生效。
|
认证码授权
在 OAuth 2.1 规范 中,有关于认证码授权的详细介绍。
这个流程支持通过某个真正的 Jmix 用户许可而获得访问 token。当使用 Google、Facebook 或任何其他网站进行授权时,使用的就是认证码授权。Jmix 应用程序会展示一个特殊的登录表单,Jmix 对凭证进行验证后,如果验证通过,则访问 token 会用客户端应用和 Jmix 应用的一系列 HTTP 请求进行发送。
如需在 Jmix 中启用这种授权类型,需要定义一个支持这种授权类型的客户端。
spring.security.oauth2.authorizationserver.client.myapp.registration.client-id=myapp
spring.security.oauth2.authorizationserver.client.myapp.registration.client-secret={noop}myappsecret
spring.security.oauth2.authorizationserver.client.myapp.registration.client-authentication_methods=client_secret_basic
# enable required grant types
spring.security.oauth2.authorizationserver.client.myapp.registration.authorization-grant-types=authorization_code,refresh_token
# in this example we use the https://oauthdebugger.com website for testing
spring.security.oauth2.authorizationserver.client.myapp.registration.redirect-uris=https://oauthdebugger.com/debug
# use opaque tokens instead of JWT
spring.security.oauth2.authorizationserver.client.myapp.token.access-token-format=reference
# access token time-to-live
spring.security.oauth2.authorizationserver.client.myapp.token.access-token-time-to-live=1h
# refresh token token time-to-live
spring.security.oauth2.authorizationserver.client.myapp.token.refresh-token-time-to-live=24h
# use PKCE when performing the Authorization Code Grant flow
spring.security.oauth2.authorizationserver.client.myapp.require-proof-key=true
需要注意,默认的 Spring 认证服务会配置以下端点的 URL:
-
认证端点:
/oauth2/authorize
,例如,http://localhost:8080/oauth2/authorize
-
token 端点:
/oauth2/token
,例如,http://localhost:8080/oauth2/token
在下面的示例中,我们将使用 https://oauthdebugger.com 网站测试 token 的分发。这个网站将模拟一个访问 Jmix 资源的外部应用,需要获取访问 token。在你自己的应用中,需要实现 OAuth 2.1 协议规范中提到的步骤:发送请求至认证端点、处理重定向、解析认证码、发送请求至 token 端点用认证码获取访问 token 等。
打开 https://oauthdebugger.com,填写下列字段:
-
Authorize URI: http://localhost:8080/oauth2/authorize
-
Client ID: myapp
-
Scope: 留空
-
Use PKCE?: 勾选这个复选框。

请记下 Code Verifier 字段的值,后续我们会用到。
点击页面底部的 SEND REQUEST 按钮。
此时会展示 Jmix 应用程序的一个特殊登录表单,这里需要填写已有 Jmix 用户的凭证。

完成所有步骤后获取的访问 token 将关联至该用户,所有发送至 Jmix REST API 的请求都会检查该用户的权限(资源角色和行级角色)。
如果凭证有效,网页会重定向至 URI https://oauthdebugger.com/debug,这个地址在 application.properties
定义。认证码必须以 code
URL 参数进行添加,例如:https://oauthdebugger.com/debug?code=BdgQArzTaj_xna_a0-PoUIQwszMR0xPkToxcktd5wPe4SbO18qBYStqJePOPNaoe9cuIJe0nac0cw0yVC9Iv3SeofEYbMZhMKldoJQQwcBUnBTfp2AyQayDlaE8KPaCf&state=sujodv3j7eh
如需用这个认证码获取访问 token,则需要执行另一个发送至 Jmix 应用 token 端点的 HTTP 请求。这里我们用 curl
命令行工具,使用 https://oauthdebugger.com 初始网页的 code_verifier
值。
curl -X POST http://localhost:8080/oauth2/token \
--basic --user myapp:myappsecret \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "redirect_uri=https://oauthdebugger.com/debug" \
-d "code=c9ehHTJyT84mX-v2v2Q8sbAxkAFYg-gjfZDJImu5ExZVGLUyWn_J2-afs_m7kiv7MwjD-XXVRQtwz_6H-JTb4NvuWiUw6-5vrF75LtyNYAovuvSJQ680nQwv3PbhB4Y-" \
-d "code_verifier=zdhRZIStXgwonFfvNYo2oI6nYuYt022LdcZF8eh3LGE" \
-d "code_challenge_method=S256"
结果如下:
{
"access_token":"Q6zvq8qGMUrN1VgouerOp4TJrry2f8oqL6mix8lDW-VKD_JHZXx0xv-ZZ_Zg_qgaHNw_wmeX6Qs0SlvEiFCyHqJ-PjqsnNkfF1XNKCAV43GQO0QeqmuV2sMiLgzY-m5r",
"refresh_token":"DSINNaxmYykPrs3bDaKqaRgnrQDeZYInEF0yjtj2Vzkf5Nbf7OA0N09uQFN97MUmqaHBIXVxJFPQHtIbn-BM6Di035P68NqiIVfCawR5m6qQ6HbD6pQsCqAo-FBYAMqv",
"token_type":"Bearer",
"expires_in":299
}
刷新码授权
关于刷新码授权可以阅读 OAuth 2.1 规范。需要为客户端注册 refresh-token
授权类型:
# enable required grant types
spring.security.oauth2.authorizationserver.client.myapp.registration.authorization-grant-types=authorization_code,refresh_token
如需用刷新码获取新的访问 token,则需要执行一个发送至 Jmix 应用 token 端点的 HTTP 请求。这里我们用 curl
命令行工具。
curl -X POST http://localhost:8080/oauth2/token \
--basic --user myapp:myappsecret \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=zN2i5JooLfi0iqNJzaE-iiEiC2oHStv_X-kOaLuqX6ZNyRCs0EaNLik1xZrz-TPHfNEahLS2c402S_1kAO09K2x6oi3LFgpFoyr9snwE3ZXJ3Lp5AVH7s4YUBOXi0VRc"
匿名访问
默认情况下,所有的端点都需要在应用程序中认证成功才能访问。
但是,我们也可以将某些 REST API 端点开放为匿名接口。这其实是使用了 Jmix 的匿名访问功能。此时,API 请求是通过 anonymous
用户进行,这个用户是 Jmix 应用默认配置的。
对于没有使用 Authentication
请求头调用任何安全端点,都会使用 anonymous
用户进行认证。
如需添加匿名访问端点的白名单,可以使用 jmix.rest.anonymous-url-patterns 应用程序属性配置,URL pattern 以逗号隔开。例如:
jmix.rest.anonymous-url-patterns = \
/rest/services/productService/getProductInformation,\
/rest/entities/Product,\
/rest/entities/Product/*
上面示例中配置的最后一个 pattern 是必须的,因为在更新或删除 Product
实体时,URL 还带有 id 部分。
配置完成后,可以不使用 Authentication
请求头与 ProductService
进行交互:
GET {{baseRestUrl}}
/services
/productService
/getProductInformation
?productId=123
# Authorization: not set
这个请求会成功拿到服务的返回内容:
{
"name": "Apple iPhone",
"productId": "123",
"price": 499.99
}
如需为某些 实体 端点提供匿名访问,请确保 anonymous
用户有访问这些实体的权限。可以创建一个 资源角色 然后分配给 anonymous
用户,代码中使用 DatabaseUserRepository.initAnonymousUser()
方法。示例:
@ResourceRole(name = "AnonymousRestRole", code = AnonymousRestRole.CODE, scope = "API")
public interface AnonymousRestRole {
String CODE = "anonymous-rest-role";
@EntityAttributePolicy(entityClass = Product.class,
attributes = "*",
action = EntityAttributePolicyAction.MODIFY)
@EntityPolicy(entityClass = Product.class,
actions = {EntityPolicyAction.READ, EntityPolicyAction.UPDATE})
void product();
}
@Primary
@Component("UserRepository")
public class DatabaseUserRepository extends AbstractDatabaseUserRepository<User> {
// ...
@Override
protected void initAnonymousUser(User anonymousUser) {
Collection<GrantedAuthority> authorities = getGrantedAuthoritiesBuilder()
.addResourceRole(AnonymousRestRole.CODE)
.build();
anonymousUser.setAuthorities(authorities);
}
}
匿名访问功能 不需要 anonymous 用户有 rest-minimal 角色。
|