用户认证

用户认证是与 REST API 交互的第一步。Jmix 必须通过这一步来识别用户,哪个用户发起了请求。Jmix 依据用户对请求添加权限/数据访问限制,以确保仅允许用户查看或者交互系统中允许的部分内容。

认证请求

如需使用 Jmix REST API 交互,需要经过 OAuth2 流程进行认证。下面的序列图解释了用 REST API 的一般交互过程:

Diagram

首先,API 客户端需要使用认证请求获取一个访问 token。给 Jmix 发送一个 www-form-urlencoded 请求,请求的表单中包含 username 和 password:

认证请求
POST http://localhost:8080/oauth/token
Authorization: Basic {{client_id}} {{client_secret}} (1)
Content-Type: application/x-www-form-urlencoded (2)

grant_type=password (3)
&username={{username}} (4)
&password={{password}}
1 认证请求本身也需要使用基本的 client_idclient_secret 进行认证。
2 认证请求的 content type 为 application/x-www-form-urlencoded
3 grant_typepassword,指定认证类型。
4 用户的凭证,使用表单的 usernamepassword 键值提供。

成功登录后,Jmix 在 HTTP 响应中返回访问 token:

HTTP/1.1 200
{
  "access_token": "CXE0w/9cOsnpSo8v2jEDoI8Qa3Y=",
  "token_type": "bearer",
  "refresh_token": "Hh2xCuZ7fgd35obagEBNGevF4ws=",
  "expires_in": 31535999,
  "scope": "rest-api",
  "OAuth2.SESSION_ID": "5C46CDF266E8C8C15372887830B74F59"
}

access_token 属性值为访问 token,可以用在后续其他请求中。

更新 Token

Token 只是用作临时的凭证/密码。一段时间之后,token 就会过期,你需要请求一个新的 token。一个方法是要求用户使用凭证再做一次常规的认证请求。

不要为了图方便在你的客户端应用程序中存储用户的 username 和 password,以便用户不用再次输入凭证。如果需要的话,应该再次要求用户输入,或者使用 refresh token 防止 token 过期。

最初的认证请求返回体中包含一个 refresh_token,可以用来创建新的访问 token。访问 token 的有效期相对比较短(Jmix 默认为 12 小时),而刷新 token 的有效期默认为 365 天。因此,可以刷新 token 非常适合用来做长期有效的凭证以获取新 token。

如需使用刷新 token 获取访问 token,可以用获取 token 的相同接口,但是 grant_type 不是 password 而是 refresh_token。下面是使用刷新 token 获取新访问 token 的示例:

Refresh Authentication Request
POST http://localhost:8080/oauth/token
Authorization: Basic {{client_id}} {{client_secret}}
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token (1)
&refresh_token=Hh2xCuZ7fgd35obagEBNGevF4ws= (2)
1 grant_typerefresh_token,表示登录的类型。
2 刷新 token 使用 refresh_token 键值提供。

返回结果包含一个新的访问 token,具有新的过期时间。使用这种方式,在 token 过期后无需向用户索要凭证继续使用 API。

发起 API 请求

对不同场景的 Jmix API,有两种使用访问 token 进行认证的方式。我们先看第一种主要的使用方式:通过 HTTP 请求头提供。

请求头认证

收到访问 token 后就可以发起 REST API 请求。访问 token 要在每个请求头的 Authorization 中提供,示例:

Create Customers Request
POST http://localhost:8080/rest
            /entities
            /rstex11_Customer
Authorization: Bearer CXE0w/9cOsnpSo8v2jEDoI8Qa3Y=  (1)

{
  name: "Randall Bishop"
}
1 API 请求使用 Bearer 类型和访问 token 进行认证。

URL 参数认证

访问 token 还可以用于 URL 查询参数中。主要用在 HTTP 请求头无法设置的情况。比如,提供浏览器中的文件链接,或者渲染图片时。

下面的示例中,来自 文件 API 的图片需要在网页的 <img src="…​" /> 中展示。

此时,无法设置 HTTP 请求头,因此可以将 access_token 作为 URL 参数传入:

<img
    src="http://localhost:8080/files
            ?access_token=CXE0w/9cOsnpSo8v2jEDoI8Qa3Y=
            &fileRef=fs://2021/03/12/a3b6011d-9040-151e-7d17-f7ccdf75d72f.jpg?name=cat.jpg"
/>

匿名访问

默认情况下,所有的接口都需要先进行用户认证之后才能使用。但是也支持使用 Jmix 的匿名访问功能将某些 REST API 开放为匿名接口。此时,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/*

上面配置的最后一行是必要的,因为如果是更新或者删除 Product 实体,URL 最后还有一部分是实体 ID。

设置完成后,可以无需使用 Authorization 请求头与 ProductService 进行交互:

GetProductInformation Request
GET {{baseRestUrl}}
         /services
         /productService
         /getProductInformation
         ?productId=123
# Authorization: not set

该请求会成功收到服务的响应:

HTTP/1.1 200
{
  "name": "Apple iPhone",
  "productId": "123",
  "price": 499.99
}

如需提供对某些 实体 接口的匿名访问,请确保 anonymous 用户有访问这些实体的权限。可以创建一个 资源角色,然后在 DatabaseUserRepository.initAnonymousUser() 方法中为 anonymous 用户分配该角色。示例:

@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 角色。