我也挺喜欢前后端分离来开发项目的,虽然都得自己干。

前、后端分离

随着前端设备(智能手机、IPAD、平板、笔记本、摄像头、智能家具等)的及物联网的发展,前端的形式变得更加多样化:

在这种时代的大背景下,传统的 在前端代码中嵌入后端代码的混合式开发 已经无法满足目前的情况了。

前端技术慢慢从后端开发中分离了出来,前端只关注前端页在,后端只负责数据的处理,后端负责提供数据接口与前端进行通信,通信时一般使用 json/xml 格式。

既然前、后端通过接口进行沟通,那么我们就要为接口定义一个规范,如果所有的接口都满足同样的规范,就可以降低沟通和开发的成本。

REST 规范

RESTful 是一种软件设计风格,由 Roy Fielding 在他的 论文 中提出,全称为 Representational State Transfer,直译为表现层状态转移

RESTful 风格的接口,目前来看,实现的最好的就是 Github API,经常被效仿。接下来我们通过分析 Github API 来引出我们的 API 设计原则。

使用 HTTPS

HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版。

HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

HTTPS 为接口的安全提供了保障,可以有效防止通信被窃听和篡改。

域名

应当尽可能的将 API 与其主域名区分开,可以使用专用的域名,访问我们的 API,例如:

1
https://api.xxx.com

或者可以放在主域名下,例如:

1
https://www.xxx.com/api

版本控制

随着业务的发展,需求的不断变化,API 的迭代是必然的,很可能当前版本正在使用,而我们就得开发甚至上线一个不兼容的新版本,为了让旧用户可以正常使用,为了保证开发的顺利进行,我们需要控制好 API 的版本。

通常情况下,有两种做法:

  • 将版本号直接加入 URL 中
1
2
https://api.xxx.com/v1
https://api.xxx.com/v2

接口命名

规则一、使用 HTTP 动词代表操作的类型。

动词 描述
GET 获取资源,单个或多个
POST 创建资源
PUT 更新资源,客户端提供完整的资源数据
PATCH 更新资源,客户端提供部分的资源数据
DELETE 删除资源

GitHub 网站的一些例子:

1
2
3
4
5
6
7
GET /issues                                      列出所有的 issue
GET /repos/:owner/:repo 列出某个项目的 信息
GET /repos/:owner/:repo/issues/:number 获取某个项目的某个 issue
POST /repos/:owner/:repo/issues 为某个项目创建 issue
PATCH /repos/:owner/:repo/issues/:number 修改某个 issue
PUT /repos/:owner/:repo/issues/:number/lock 锁住某个 issue
DELETE /repos/:owner/:repo/issues/:number/lock 接收某个 issue

说明:冒号(:xx))开始的代表变量,例如 /repos/fortheday001/jxshop

规则二、接口地址的命名应该是名词(一般是复数形式)

错误的例子:

1
2
3
4
POST https://api.xxx.com/createTopic
GET https://api.xxx.com/topic/show/1
POST https://api.xxx.com/topics/1/comments/create
POST https://api.xxx.com/topics/1/comments/100/delete

正确的例子(名词):

1
2
3
4
POST https://api.xxx.com/topics
GET https://api.xxx.com/topics/1
POST https://api.xxx.com/topics/1/comments
DELETE https://api.xxx.com/topics/1/comments/100

资源过滤

当我们需要搜索、排序、分页等过滤数据时我们需要在 URL 传合适的参数:

1
2
3
?state=closed: 不同状态的资源
?page=2&per_page=100:访问第几页数据,每页多少条。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。

比如获取10条用户的请求地址为:

1
GET /users?per_page=10

正确使用状态码

接口在返回数据时应该正确的返回 HTTP 对应的状态码,常用的状态码如下:

常用状态码:

1
2
3
4
5
6
7
$_http_code = [
200 => "OK", // 成功
400 => "Bad Request", // 请求数据有问题
401 => "Unauthorized", // 未登录
403 => "Forbidden", // 登录但没有权限
404 => "Not Found", // 请求数据没找到
];

数据响应格式

接口应该返回 JSON 或者 XML 格式的数据。

返回的数据的结构应该有一个固定的结构,这个结构可以自己设计,比如:

1
2
3
4
5
'message' => ':message',          // 错误的具体描述
'errors' => ':errors', // 参数的具体错误描述,422 等状态提供
'code' => ':code', // 自定义的异常码
'status_code' => ':status_code', // http状态码
'debug' => ':debug', // debug 信息,非生产环境提供

例如:

1
2
3
4
5
6
7
8
9
{
"message": "422 Unprocessable Entity",
"errors": {
"name": [
"姓名 必须为字符串。"
]
},
"status_code": 422
}
1
2
3
4
{
"message": "您无权访问该订单",
"status_code": 403
}

频繁限制

为了防止服务器被攻击,减少服务器压力,需要对接口进行合适的限流控制,需要在响应头信息中加入合适的信息,告知客户端当前的限流情况

  • X-RateLimit-Limit :100 最大访问次数
  • X-RateLimit-Remaining :93 剩余的访问次数
  • X-RateLimit-Reset :1513784506 到该时间点,访问次数会重置为 X-RateLimit-Limit

示例:

1
2
3
4
5
6
7
curl -i https://api.github.com/users/octocat
HTTP/1.1 200 OK
Date: Mon, 01 Jul 2013 17:27:06 GMT
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 56
X-RateLimit-Reset: 1372700873

超过限流次数后,需要返回 429 Too Many Requests 错误。

编写文档

为了方便用户使用,我们需要提供清晰的文档,尽可能包括以下几点

  • 包括每个接口的请求参数,每个参数的类型限制,是否必填,可选的值等。
  • 响应结果的例子说明,包括响应结果中,每个参数的释义。
  • 对于某一类接口,需要有尽量详细的文字说明,比如针对一些特定场景,接口应该如何调用。

Postman

postman 是一个接口访问软件,非常适合服务器端程序员用来测试接口、发布接口文档等。

集合管理

我们可以在左侧创建接口集合,将项目中所有的接口都统一分类管理起来:

image-20181121143604525

环境变量

我们可以在软件中管理环境变量,环境变量可以让我们把系统的IP地址、JWT字符串等常用的内容定义到变量中保存,然后通过变量来使用这个数据,就不用每次都填写了:

添加环境变量:

添加之后,就可以通过 来使用了:

保存示例数据

当我们使用 postman 请求接口之后,会为我们美化服务器返回的数据:

返回数据之后,我们可以点击 Save Response 按钮保存返回的数据,保存的目的是在制作接口文档时,这些数据可以做为示例数据,保存时可以设置一个名字:

生成接口文档

postman 还有一个好用的功能就是可以帮助我们生成 接口文档

当我们编写好接口以及保存好接口返回的数据示例之后,我们就可以使用它来生成 接口文档:

点击 Publish Docs 生成接口文档时,先选择我们要使用的环境变量:

选择完之后就可以发布了,发布之后,我们就得到发布之后的浏览地址:

把这个接口地址发给前端程序员就可以了:

接口文档地址:https://documenter.getpostman.com/view/3908128/RzZDjxgP

Laravel

定义路由

在做接口开发时,所有的接口都定义在 routes/api.php 文件中。

定义在 routes/api.php 文件中的路由有几下几个特点:

1、接口地址中必须加上 api 前缀,比如:http://127.0.0.1:8000/api/goods

2、没有 csrfsessioncookie等传统网站开发中的常用功能(这些功能使用 jwt(json web token)(令牌)替代)

3、所有接口都会有频率限制(每分钟60次)(通过 Laravel 中自带的 throttle 中间件实现)

频率限制

routes/api.php 文件中定义的路由都会默认被应用 throttle 中间件,同一个IP1分钟内最多访问一个接口60次。

image-20181123091223386

如果要为不同的接口设置不同的频率,可以在 routes/api.php 文件中定义路由时,先定义路由组,然后为这个路由组设置频率,所有这个组中的路由都会被应用同样的频率限制:

1
2
3
4
5
6
7
8
9
10
11
12
// 1分钟最多5次
Route::group(['prefix'=>'api','middleware'=>'throttle:5'],function(){
Route::get('users',function(){
return \App\User::all();
});
});
// 10分钟最多5次
Route::group(['prefix'=>'api','middleware'=>'throttle:5,10'],function(){
Route::get('users',function(){
return \App\User::all();
});
});

AJAX跨域

在前、后端分离开发时,前端都是使用 ajax 与服务器端进行通信的,但是 ajax 有一个限制:不能跨域名访问服务器接口

在实现工作中,前端和后端经常是部署在不同的服务器上,也就都拥有不同的域名,这时前端发送 AJAX 就会报错,比如:前端在 127.0.0.1:8000 这个域名下,而后端在 127.0.0.1:8080 这个端口下时:

解决办法:

目前常用的解决办法有 jsonpcors

JSONP:只支持 GET 请求的跨域。

CORS:(跨站资源共享),实现原理是在服务器端设置几个 http 协议头即可,缺点是有些老旧的浏览器不支持。

Laravel 中使用 CORS:

如果每次发请求时都手动设置协议头太麻烦了,所以可以使用 Laravel 中间件来实现。

Laravel 的中间件会在接收和发送请求时自动被调用,这样就可以只编写一次代码就可以在每次请求时自动设置协议头了。

1、创建中间件

1
php artisan make:middleware CorsMiddleware

2、编写代码

app\Http\Middleware\CorsMiddleware.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function handle($request, Closure $next)
{
$response = $next($request);
$origin = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
// 允许访问的域名列表
$allow_origin = [
'http://localhost:8080',
];
// 通过 $response->header 设置协议头
// (扩展:如果想要允许所有域名跨域访问,就可以去掉if判断,然后
// 直接设置 Access-Control-Allow-Origin : *)
if (in_array($origin, $allow_origin)) {
// 如果要允许所有域名跨域访问,设置把这一项设置为 *
$response->header('Access-Control-Allow-Origin', $origin);
$response->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS');
$response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
$response->header('Access-Control-Expose-Headers', 'Authorization, authenticated');
$response->header('Access-Control-Allow-Credentials', 'true');
}
return $response;
}

3、注册中间件

app\Http\Kernel.php 文件中注册全局中间件。

注册到 $middleware 数组中的中间件会在所有请求时会自动调用。

1
2
3
4
5
6
7
8
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CorsMiddleware::class
];

发送 HTTP 状态码

在 Laravel 框架中可以直接使用 response 方法向前端发送数据和状态码信息。

1
return response('无权访问!'403);

这时框架会在发送数据时设置一个 403 的协议头信息,当浏览器收到非 2xx 开头的状态码时会显示显示错误信息:

当我们使用 axios 请求这个地址时,如果服务器返回的是非 2xx 开头的状态码,那么就会认为发生了错误,错误信息需要在 catch 中获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
axios.get('http://127.0.0.1:8000/api/test')
.then((res)=>{
// 服务器返回了 2xx 状态码(代码成功时执行)
console.log( res.data )
})
.catch((err)=>{
if (err.response) {
// 服务器返回了一个非 2xx 的状态码时
console.log(err.response.data);
console.log(err.response.status);
console.log(err.response.headers);
} else if (err.request) {
// 发送请求时出错(没有发送成功)
console.log(err.request);
} else {
// 设置 axios 时出错(没有发送成功)
console.log('Error', err.message);
}
console.log(err.config);
}

发送自定义状态码

上面的方式是在 HTTP 协议头中发送状态码信息,这种方式的缺点的是:如果不是 2xx 的状态码,浏览器就会显示错误 这样的用户体验不太好。

我们可以在程序中无论正确与否都发送 200 状态码(成功),然后把错误的状态码信息放到我们自定义的数据中返回给前端,然后前端通过我们定义的状态码来判断对错,这样浏览器就不会显示错误信息了:

比如:我们可以在项目中定义两个函数,成功时调用 ok 向前端发数据 ,调用时使用 error 向后端发消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function ok($data)
{
return [
'status_code' => 200,
'message'=>'ok',
'data' => $data
];
}
function error($error, $code)
{
static $_http_code = [
400 => "Bad Request", // 请求数据有问题
401 => "Unauthorized", // 未登录
403 => "Forbidden", // 登录但没有权限
404 => "Not Found", // 请求数据没找到
422 => "Unprocessable Entity", // 无法处理输入的数据
];
return [
'status_code' => $code,
'message' => $_http_code[$code],
'errors' => $error
];
}

然后在程序中我们可以这样显示数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TestController extends Controller
{
public function test(Request $req) {

$validator = Validator::make($req->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);

if ($validator->fails()) {
return error($validator->errors(), 422);
}

return ok([
'name' => 'tom',
'age' => 20,
]);
}
}

这时前端收到的消息时,同样返回了 422 错误的状态码,只不过这次不是在 http 协议头中返回的,是在我们正常的数据中返回的,这样浏览器就不会显示错误信息了:

在 Laravel 框架中添加自定义函数

有时我们为了让 Laravel 框架更加的好用,我们会向框架中添加一些自定义的函数,那么这些自定义的函数应该放在哪里?其实,我们可以这样做:

1、创建 app\helpers.php 文件保存我们自定义的函数

app\helpers.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
function ok($data)
{
return [
'status_code' => 200,
'message'=>'ok',
'data' => $data
];
}
function error($error, $code)
{
static $_http_code = [
400 => "Bad Request", // 请求数据有问题
401 => "Unauthorized", // 未登录
403 => "Forbidden", // 登录但没有权限
404 => "Not Found", // 请求数据没找到
422 => "Unprocessable Entity", // 无法处理输入的数据
500 => "Internal Server Error", // 服务器内部错误
];
return [
'status_code' => $code,
'message' => $_http_code[$code],
'errors' => $error
];
}

2、修改 composer.json 添加自动加载项

composer.json

1
2
3
4
5
6
"autoload": {
...
"files": [
"app/helpers.php"
]
},

3、命令行中执行加载指令

1
composer dump-auto

4、重新启动 Laravel 框架然后就可以在项目中直接使用 app\helpers.php 文件中定义的函数了。

表单验证

因为 Laravel 框架中的表单验证默认的行为是 验证失败就跳转回上一个页面 ,而在做接口开发时我们需要返回 JSON 数据而不是跳转,这时我们有多种解决办法,比如:我们可以采用 手动验证 的方式来验证数据,然后自己来控制在验证失败时返回 JSON 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 自己创建验证器
$validator = Validator::make($req->all(), [
'name' => 'required',
'tel' => 'required|regex:/^1[34578][0-9]{9}$/',
'province' => 'required',
'city' => 'required',
'area' => 'required',
'address' => 'required',
'is_default' => 'required',
]);

// 如果验证失败返回 json 数据
if ($validator->fails()) {
return error($validator->errors(), 422);
}

JWT

在 Laravel 框架中我们可以使用 php-jwt 这个扩展包来生成、验证、解析令牌。

生成令牌

1、安装扩展包

1
composer require firebase/php-jwt

2、.env 中定义加密密钥

.env

1
2
JWT_KEN=fdsa32@#RFSDafpeq3r2fews8d783f;fa/fd293f
JWT_EXPIRE=7200

3、生成令牌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Firebase\JWT\JWT;
...

// 读取密钥
$key = env('JWT_KEY');
// 当前时间戳
$now = time();
// 加密数据
$data = array(
"iat" => $now, // 当前时间
"exp" => $now + env('JWT_EXPIRE'), // 过期时间
"id" => 1, // 用户ID
// 其它需要保存的数据 ...
);
// 生成 JWT
$jwt = JWT::encode($data, $key);

令牌验证

在 Laravel 框架中我们可以使用中间件来验证、解析令牌,然后将解析之后的数据保存到 $request->jwt 属性中,然后在项目中就可以使用 $request->jwt 来获取 JWT 中的数据了。

1、创建中间件

app\Http\Middleware\Jwt.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace App\Http\Middleware;

use Closure;
use \Firebase\JWT\JWT as JWTCHECK;

class Jwt
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$jwt = substr($request->server('HTTP_AUTHORIZATION'), 7);
try
{
$request->jwt = JWTCHECK::decode($jwt, env('JWT_KEY'), array('HS256'));
return $next($request);
}
catch(\Exception $e)
{
return response([
'code'=>'403',
'message'=>'HTTP/1.1 403 Forbidden'
]);
}
}
}

2、注册中间件

app\Http\Kernel.php

1
2
3
4
5
6
7
8
9
10
11
12
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'jwt' => \App\Http\Middleware\Jwt::class,
];

3、注册路由时使用中间件

routers/api.php

1
2
3
4
5
6
Route::post('/login', 'MemberController@login');

// 应用 jwt 中间件的路由
Route::middleware(['jwt'])->group(function () {
Route::get('/orders', 'OrderController@index');
});

接口实现

接下来我们学习一下如何使用 Laravel 框架实现接口的开发。

配置

首先我们需要修改 .env 文件配置上数据库的账号:

1
2
3
4
5
6
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=jxshop
DB_USERNAME=root
DB_PASSWORD=123456

配置好之后注意:需要重新启动服务

注册接口

1、首先我们在 postman 软件中定义接口的名称、地址、参数等信息:

说明:

  • 编写接口是先创建集合,然后再创建二级目录来管理接口(接口多时好找)
  • 接口是 post 时需要在 body 中设置需要提交的数据,一般使用 x-www-form-urlencoded 方式提交数据
  • 通过环境变量来管理常用的数据,比如使用 host 变量保存接口地址

2、创建环境变量

在使用 postman 测试接口时,有些数据使用的频率比较高,比如:服务器地址、令牌等,这时我们就可以定义环境变量来保存这些数据,方便修改和维护。

打开环境变量管理面板:

添加一套环境变量:

输入环境变量的名字以及定义的变量和值:

创建之后选择使用环境变量:

接口写好之后就可以在 Laravel 中编写代码实现接口了。

3、配置路由

routes/api.php

1
Route::post('members', 'MemberController@insert');

4、创建模型

使用 artisan 指令创建模型并保存到 Models 目录中:

1
php artisan make:model Models/Member

模型中配置基本信息:

app\Models/Member.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Member extends Model
{
// 对应的表单
protected $table = 'members';
// 表中是否有两个时间字段(created_at和updated_at)
public $timestamps = true;
// 设置允许填充的字段
protected $fillable = ['username','password'];
// 需要隐藏的字段(不会发给前端的字段)
protected $hidden = ['password','updated_at','created_at'];
}

5、创建控制器

1
php artisan make:controller MemberController

app\Controller\MemberController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Validator;
use App\Models\Member;

class MemberController extends Controller
{
public function insert(Request $req)
{
// 生成验证器对象
// 参数一、表单中的数据
// 参数二、验证规则
$validator = Validator::make($req->all(), [
'username'=>'required|min:6|max:18|unique:members',
'password'=>'required|min:6|max:18|confirmed',
]);

// 如果失败
if($validator->fails())
{
// 获取错误信息
$errors = $validator->errors();
// 返回 JSON 对象以及 422 的状态码
return error($errors, 422);
}

// 插入数据库
// 返回值:插入成功之后那条记录的对象
$member = Member::create([
'username' => $req->username,
'password' => bcrypt($req->password),
]);

return ok($member);

}
}

6、在 postman 中测试接口

保存返回的结果(失败、成功的结果都保存一份):

7、发布文档

登录接口

1、postman 中定义接口地址、参数等

2、定义路由

routes/api.php

1
Route::post('authorizations', 'MemberController@login');

3、控制器中添加登录方法

app\Http\Controllers\MemberController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public function login(Request $req)
{
$validator = Validator::make($req->all(), [
'username'=>'required|min:6|max:18',
'password'=>'required|min:6|max:18',
]);
if($validator->fails())
{
// 获取错误信息
$errors = $validator->errors();
// 返回 JSON 对象以及 422 的状态码
return error($errors, 422);
}

// 根据用户名查询账号是否存在 (只查询一条用 first 方法)
$member = Member::select('id','password')->where('username',$req->username)->first();
if($member)
{
// 判断密码
if(Hash::check($req->password, $member->password))
{
// 把用户的信息保存到令牌(JWT)中,然后把令牌发给前端
$now = time();
// 读取密钥
$key = env('JWT_KEY');
// 过期时间
$expire = $now + env('JWT_EXPIRE');
// 定义令牌中的数据
$data = [
'iat' => $now, // 当前时间
'exp' => $expire, // 过期时间
'id' => $member->id,
];
// 生成令牌
$jwt = JWT::encode($data, $key);

// 发给前端
return ok([
'ACCESS_TOKEN' => $jwt,
]);
}
else
{
return error('密码不正确!', 400);
}
}
else
{
return error('用户名不存在!', 404);
}
}

4、下载 JWT 的包

登录时需要 JWT 令牌机制,所以需要先安装一个解析 JWT 的包:

1
composer require firebase/php-jwt

5、配置 JWT

.env 文件中定义 jwt 的密钥和过期时间

1
2
JWT_KEN=fdsa32@#RFSDafpeq3r2fews8d783f;fa/fd293f
JWT_EXPIRE=7200

6、保存示例结果

在 Postman 中把几种不同情况的返回结果都保存起来:

登录成功时返回令牌:

令牌的验证

前端在登录成功之后,会得到令牌,然后需要把得到的令牌保存到本地,之后在请求需要验证的接口时(比如下单)需要把这个令牌在 HTTP 协议头中发送到服务器端,以进行令牌验证。

服务器端验证

我们可以使用 Laravel 框架的中间件来实现令牌的验证。

1、创建中间件

1
php artisan make:middleware Jwt

并编写中间件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

namespace App\Http\Middleware;

use Closure;
use \Firebase\JWT\JWT as JWTCHECK;

class Jwt
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{

/* 说明:客户端在提交令牌时,是把令牌放到 http 协议头中(不是表单中!!)
并且 JWT 规定前7个字符必须是 bearer (后面这里有个空格)
HTTP_AUTHORIZATION: bearer fdkl;ajsf;dsajlfjl;jxxxxx
所以我们在获取令牌时,要从 $_SERVER 中获取,不是 $_POST
在 Laravel 中要获取 $_SERVER 使用 $request->server 函数
*/
// 从协议头是取出令牌
$jwt = substr($request->server('HTTP_AUTHORIZATION'), 7);
try
{
// 解析 token
$jwt = JWTCHECK::decode($jwt, env('JWT_KEY'), array('HS256'));
// 把解析出来的数据保存到 Request 对象中的 jwt 属性上,将来在控制器中就可能 $req->jwt 这样来获取了
$request->jwt = $jwt;

// 继续执行下一个中间件
return $next($request);
}
catch(\Exception $e)
{
// 返回错误信息
return response([
'code'=>'403',
'message'=>'HTTP/1.1 403 Forbidden'
]);
}
}
}

2、注册中间件到路由中间件组中

app\Http\Kernel.php

1
2
3
4
5
6
7
8
9
10
11
12
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'jwt' => \App\Http\Middleware\Jwt::class,
];

3、在定义路由时应用中间件

routes/api.php

先定义一个路由组,然后对组应用 jwt 中间件,所有这个组中的路由都会先验证令牌

1
2
3
4
5
Route::middleware(['jwt'])->group(function () {

Route::post('orders', 'MemberController@order');

});

4、控制器中获取令牌数据

因为在中间件中我们已经把解析令牌的数据保存到 Request 对象的 jwt 属性中了,所以在控制器中可以直接使用 $req->jwt 来获取令牌中的数据:

1
2
3
4
5
6
public function order(Request $req)
{
// 获取令牌中的数据

echo $req->jwt->id;
}

Postman 中设置令牌

当使用 Postman 来请求需要验证的接口时,需要先在 Postman 中设置令牌,否则 会请求失败。

1、先使用正确的账号和密码登录成功得到一个令牌

2、把令牌保存到环境变量中

3、在需要令牌的接口上设置 authorzation

这时就可以正常的访问令牌了。

参考自我的课程讲义

 评论



本站使用 Material X 作为主题 , 总访问量为 次 。
隐藏