概述

从个人角度而言,使用PHP开发接口和普通的后台开发区别不是很大。普通的后台开发是,最后直接输出HTML模板,而接口则是返回JSON。此外,请求校验也不同,后台中,可以使用session结合cookie来校验访问的用户以及状态。但是,接口并不需要记住状态,只需要认证接口的合法性。目前做过的一个项目来说,就是这么干的,使用一套东西来同时满足接口和后台。 在Laravel中,开发API接口,可以借助Dingo/API这个库来简化很多操作。下面介绍下自己在使用中遇到的问题和处理。

Dingo/API

Dingo/API提供了一套工具可以很方便的构建APIs。

安装配置

基础安装

composer require dingo/api:1.0.x@dev

编辑config/app.php,添加对应的服务提供

<?php
'providers' => [
    Dingo\Api\Provider\LaravelServiceProvider::class
]

执行如下的命令,创建接口配置文件

php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"

下一步就是进行config/api.php的配置了

<?php
API_STANDARDS_TREE=vnd // 项目环境
API_SUBTYPE=myqpp // 子类型,全部小写
API_PREFIX=api // URL前缀
API_DOMAIN=test.memosa.xyz // 接口的域名
API_VERSION=v1 // 接口版本
...

接口路由

为了不混淆其他项目的路由,Dingo提供了自己的路由包。首先,必须实例化路由对象

<?php
$api = app('Dingo\Api\Routing\Router');

Dingo根据版本来进行分组。所以,典型的例子就是

<?php
$api->version('v1', function ($api) {
    $api->get('users/{id}', 'App\Api\V1\Controllers\UserController@show');
});
$api->version('v2', function ($api) {
    $api->get('users/{id}', 'App\Api\V2\Controllers\UserController@show');
});

这里,就可以在程序中访问定义的接口了。

踩的坑

安装,一切都挺顺利的,踩了几个坑。第一个是没有认真阅读文档。我的子域名test.memosa.xyz配置的是我的后台地址,然后配置config/api.php时,没有注意到API_PREFIXAPI_DOMAIN只能配置一个。按照官方的解释是,一般的接口访问,要么是使用一个独立的子域名作为接口域名,或者是在子域名后面加上后缀这两种方式。所以,这里只能配置一个。我配置了两个,导致的结果是,访问正常的test.memosa.xyz时,不能正确的获取当前的路由。

接口校验

配置完了Dingo/API之后,就可以访问接口了。但是实际中,为了安全性等,还需要对访问的接口进行验证授权。Dingo/API支持如下三种授权验证方式:

  • HTTP Basic(Dingo\Api\Auth\Provider\Basic)
  • JSON Web Tokens(JWT)(Dingo\Api\Auth\Provider\JWT)
  • OAuth2.0(Dingo\Api\Auth\Provider\OAuth2)

我使用的是JWT方式。关于JWT的介绍可以看这里。

jwt-auth

jwt-auth是Laravel的JWT库。

安装配置

编辑composer.json文件,添加如下内容

<?php
"require": {
    "tymon/jwt-auth": "0.5.*"
}

接着,执行composer更新

composer update

安装完成之后,同样的,在config/app.php中添加服务提供,修改类的别名

<?php
'providers' => [
    Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class
],
'aliases' => [
    'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
    'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class
]

创建配置文件,生成应用的Key

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
php artisan jwt:generate

授权创建Token

常见的Token授权方式,就是用户输入用户名和密码,后台匹配。匹配成功之后,返回用户的Token,失败则提示

use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;

class AuthenticateController extends Controller
{
    public function authenticate(Request $request)
    {
        // grab credentials from the request
        $credentials = $request->only('email', 'password');

        try {
            // attempt to verify the credentials and create a token for the user
            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }
        } catch (JWTException $e) {
            // something went wrong whilst attempting to encode the token
            return response()->json(['error' => 'could_not_create_token'], 500);
        }

        // all good so return the token
        return response()->json(compact('token'));
    }
}

授权部分

jwt-auth支持两种HTTP方式来授权,一种是在HTTP请求头中设置Authorization: Bearer {TOKEN};另一种是直接在URL中附加Token信息,这样,http://test.memosa.xyz/?token=blablabla

使用JWT中间件

Kernel.php中添加如下的路由中间件,这样,其他的接口访问时,都需要进行Token验证通过才能访问

<?php
protected $routeMiddleware = [
    'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
    'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
    'cors' => \App\Http\Middleware\Cors::class
];

现在就可以在路由中使用了

<?php
$api->version('v1',['middleware' => ['jwt.auth', 'cors']], function($api) {
});

踩的坑

CORS跨域资源共享(Cross Origin Resource Share)。因为浏览器的同源策略,浏览器只能访问同域名(ip),端口,协议下的内容。在使用JWT这种方式时,前端应用和接口通常不在一个域内,这样就会造成跨域访问的问题,而请求失败了。使用CORS就可以跨域访问了。使用CORS,Ajax请求,需要设置如下的信息

xhr.withCredentials = true;

后端需要设置响应的header来允许跨域访问

<?php
$headers = [
    'Access-Control-Allow-Origin' => 'http://xxx.memosa.xyz', // 设置为*时时允许所有地址的跨域请求,不安全
    'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
    'Access-Control-Allow-Credentials' => 'true'
];

最后

这些是在写一个小提醒应用时遇到的一些问题。还了解的另一点就是前后端分离的实践。前端完全可以仿照APP端,只用做界面绘制,交互。数据上,和APP请求相同的接口。这样,一套接口就可以给多端使用了。目前似乎,VueReact等就是做的这部分的东西。值得尝试。

参考内容