fullscreeninteractive/silverstripe-restful-helpers

1.2.1 2024-01-19 06:59 UTC

This package is auto-updated.

Last update: 2024-09-04 21:29:41 UTC


README

为基本的RESTful JSON API提供一些基础功能的模块。虽然大家似乎都在转向GraphQL,但如果您只需要一个快速设置,配置简单,那么简单的REST解决方案就无可匹敌。

处理认证并提供服务于解析API请求的常用功能。与silverstripe-restfulserver相比,此模块默认不提供太多模型和字段的构建脚手架,而是依赖于开发者为API布局设计(尽管提供了构建脚手架助手)。

安装

composer require fullscreeninteractive/silverstripe-restful-helpers

使用

如果您计划为API使用认证,那么首先需要配置https://github.com/Level51/silverstripe-jwt-utils/模块。

app/_config/api.yml

Level51\JWTUtils\JWTUtils:
  secret: 'replace-this-with-a-jwt-secret-for-jwt'
  lifetime_in_days: 365
  renew_threshold_in_minutes: 60

下一步是设置API的路由。您可以按项目需求修改路由的名称。至少您应该有一个特定于项目的端点,该端点将子类化ApiController,例如MyProjectsApi

app/_config/routes.yml

SilverStripe\Control\Director:
  rules:
    'api/v1/auth/$Action': 'FullscreenInteractive\Restful\Controllers\AuthController'
    'api/v1/projects//$Action': 'MyProjectsApi'

以下是一个MyProjectsApi的示例,展示了该模块提供的一些助手。任何人都可以通过GET api/v1/projects/检索所有项目的列表,登录的ADMIN用户可以通过POST api/v1/projects/create创建项目。

app/src/Project.php

<?php

use FullscreenInteractive\Restful\Interfaces\ApiReadable;
use SilverStripe\Security\Member;
use SilverStripe\ORM\DataObject;

class Project extends DataObject implements ApiReadable
{
    private static $db = [
        'Title' => 'Varchar(100)',
        'Date' => 'DBDate'
    ];

    private static $has_one = [
        'Author' => Member::class
    ];

    public function toApi(): array
    {
        return [
            'title' => $this->Title,
            'date' => $this->dbObject('Date')->getTimestamp()
        ];
    }
}

app/src/MyProjectsApi.php

<?php

class MyProjectsApi extends FullscreenInteractive\Restful\Controllers\ApiController
{
    private static $allowed_actions = [
        'index',
        'createProject',
        'deleteProject'
    ];

    public function index()
    {
        $this->ensureGET();

        return $this->returnPaginated(Project::get());
    }

    public function createProject()
    {
        $this->ensurePOST();

        $member = $this->ensureUserLoggedIn([
            'ADMIN'
        ]);

        list($title, $date) = $this->ensureVars([
            'Title',
            'Date' => function($value) {
                return strtotime($value) > 0
            }
        ]);

        $project = new Project();
        $project->Title = $title;
        $project->Date = $date;
        $project->AuthorID = $member->ID;
        $project->write();

        return $this->returnJSON([
            'project' => $project->toApi()
        ]);
    }

    public function deleteProject()
    {
        $this->ensurePOST();

        $member = $this->ensureUserLoggedIn([
            'ADMIN'
        ]);

        list($id) = $this->ensureVars([
            'id'
        ]);

        $project = Project::get()->byID($id);

        if (!$project) {
            return $this->failure([
                'status_code' => 404,
                'message' => 'Unknown project'
            ]);
        }

        if ($project->canDelete($member)) {
            $project->delete();
        }

        return $this->success();
    }
}

认证

认证通过JWT进行管理,可以存储在客户端。要接收令牌,用户必须首先通过基本认证将用户名/密码交换过来,通过带有凭证的POST请求。通常这是一种javascript请求形式

fetch('/api/v1/auth/token', {
    method: "POST",
    headers: {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Cache-control": "no-cache",
        "Authorization": "Basic " + base64.encode(email + ":" + password),
    },
})

该请求的响应将是一个错误代码(> 200),或者如果用户名和密码正确,则是一个包含JWT的200响应。令牌和相关元数据可以安全地保存在客户端以便重复使用。

{
    "token": "eyJ0eXAiOiJKV1QiL...",
    "member": {
        "id": 1,
        "email": "js@lvl51.de",
        "firstName": "Julian",
        "surname": "Scheuchenzuber"
    }
}

如果用户的令牌无效或已过期,将返回一个401错误。要验证用户的令牌,请使用verify端点,这将检查令牌并在必要时更新令牌。

fetch('/api/v1/auth/verify', {
    method: "GET",
    headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    Authorization: "Bearer " + token,
    },
})

然后可以使用该令牌作为Bearer头签名API调用。

fetch('/api/v1/projects/createProject', {
    method: "POST",
    headers: {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Cache-control": "no-cache",
        Authorization: "Bearer " + token,
    },
})

通过silverstripe-apikeys进行认证

如果您更愿意使用API密钥而不是JWT令牌,可以使用https://github.com/sminnee/silverstripe-apikey并将其配置为特定路由的中间件。

SilverStripe\Core\Injector\Injector:
  ApiRouteMiddleware:
    class: SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter
    properties:
      RequestHandler: '%$MyProjectApi'
      Middlewares:
        CustomMiddleware: '%$ApiKeyRequestMiddleware'
  MyProjectApi:
    class: MyProjectApi
  ApiKeyRequestMiddleware:
    class: Sminnee\ApiKey\ApiKeyRequestMiddleware
SilverStripe\Control\Director:
  rules:
    api:
      Controller: '%$ApiRouteMiddleware'

默认情况下,silverstripe-apikey模块不会在未提供API密钥时抛出错误(但如果提供错误的密钥会抛出错误)。因此,短期内您最好检查并处理API密钥未提供的情况。

public function projects()
{
  if (!$this->ensureUserLoggedIn()) {
    return $this->failure(401);
  }
  
  // ..
}

UUIDs

https://stackoverflow.com/questions/56576985/is-it-a-bad-practice-to-expose-the-database-id-to-the-client-in-your-rest-api/56577271

在设计API时,您可能希望避免在响应中暴露内部ID。

要为对象添加UUID字段,请将以下扩展添加到您的模型中

private static $extensions = [
    UuidableExtension::class
];

对象将在onBeforeWrite()时生成UUID。

常见问题解答

当尝试调用我的api/endpoint时,我得到了一个301重定向

在Silverstripe 5中,默认启用了CanonicalURLMiddleware以添加尾随斜杠。这可能在生产中引起问题,所以我们建议完全禁用任何API路由的此功能。

SilverStripe\Core\Injector\Injector:
  SilverStripe\Control\Middleware\CanonicalURLMiddleware:
    properties:
      enforceTrailingSlashConfigIgnorePaths:
        - 'api/'

API文档

待办事项,但规模不大。现在请参阅ApiController

许可证

BSD-3-Clause