原文:https://qianjinyike.com/laravel-api-%E8%AE%A4%E8%AF%81/
编写完全为 API 服务的 Laravel 应用,并所有响应都是 JSON 格式时,使用API认证
- 原理
注册:用户注册成功后,随机生成长字符串作为 token,原生 token 返回给用户cookie。哈希后的 token 存到数据库里。
登陆:用户使用账号密码登陆成功,随机生成长字符串作为 token,原生 token 返回给用户cookie。哈希后的 token 存到数据库里。
认证:将用户传来的 token 进行哈希,然后去数据库中查找之前存的哈希过的token进行对比 ,相等就认证成功,否则失败
- 数据库追加api_token字段并在User模型中添加白名单
$table->string('api_token', 80)->unique()->nullable()->default(null);
- 修改 api_token 这个名称
// 如果修改字段名称 api_token,请记得修改配置文件 config/auth.php 中的 storage_key
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
'storage_key' => 'api_token',
],
- 修改配置,将默认web认证改为API认证
guard 设置为 api,hash 设置为 true
// config/auth.php
'defaults' => [
'guard' => 'api', // 默认 api 认证
'passwords' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => true, // 利用 SHA-256 算法哈希你的令牌
],
- 编写 BaseRequest
首先我们需要构建一个 BaseRequest 来重写 Illuminate\Http\Request ,修改为默认优先使用 JSON 响应:
app/Http/Requests/BaseRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Http\Request;
// 注意 继承于Request而不是FormRequest,所以不能直接用artisan make:request生成
class BaseRequest extends Request
{
public function expectsJson()
{
return true;
}
public function wantsJson()
{
return true;
}
}
- 替换 BaseRequest
// 在 public/index.php 文件中,将 \Illumiate\Http\Request 替换为我们的 BaseRequest,如下:
$response = $kernel->handle(
$request = \App\Http\Requests\BaseRequest::capture()
);
PS:第五步和第六步执行后,所有的响应都是 application/json ,包括错误和异常
- Json的返回方法
方式一:使用 json() 方法#
return response()->json([
'name' => 'Abigail',
'state' => 'CA'
]);
json() 方法可以将 Content-Type 头设置为 application/ json,同时利用 PHP 内置函数 json_encode,把数组转成 JSON 格式。
方式二:直接返回数组
$arr = [1,2];
return $arr;
这种方法返回的 Content-Type 是 text/html。
上述两种方式的区别:
方式一 http Response Headers 的 Content-Type 是 application/json。
方式二 http Response Headers 的 Content-Type 是 text/html。
- 生成ApiController
<?php
// email 设置可为空
// request 和 response 都是 json 格式
// api_token 设置可插入数据库
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class ApiController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except('login', 'register');
}
// 默认使用email进行验证
protected function username()
{
// return 'name';
return 'email';
}
// 注册的时候生成一个哈希过的api_token
public function register(Request $request)
{
$this->validator($request->all())->validate();
$api_token = Str::random(80);
$data = array_merge($request->all(), compact('api_token'));
$this->create($data);
return compact('api_token');
}
protected function validator(array $data)
{
return Validator::make($data, [
// 'name' => ['required', 'string', 'max:255', 'unique:users',],
'email' => ['required', 'string', 'email', 'max:255',],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
// 等效于
// $request->validate([
// ....
// ])
}
protected function create(array $data)
{
// 实际上,属性值是否可以批量赋值需要受 fillable 或 guarded 来控制,如果我们想要强制批量赋值可以使用 forceCreate
return User::forceCreate([
// 'name' => $data['name'],
'email' => $data['email'],
'password' => password_hash($data['password'], PASSWORD_DEFAULT),
'api_token' => hash('sha256', $data['api_token']),
]);
}
public function logout()
{
// web登出是Auth::logout()
auth()->user()->update(['api_token' => null]);
return ['message' => '退出登录成功'];
}
// 先进行登录验证,之后重新生成一个哈希过的api_token并更新User表
public function login()
{
$user = User::where($this->username(), request($this->username()))
->firstOrFail();
// 将request('password')哈希后与$user->password进行比对
if (!password_verify(request('password'), $user->password)) {
return response()->json(['error' => '抱歉,账号名或者密码错误!'],
403);
}
$api_token = Str::random(80);
$user->update(['api_token' => hash('sha256', $api_token)]);
return compact('api_token');
}
// 重新生成一个哈希过的api_token并更新User表
public function refresh()
{
$api_token = Str::random(80);
auth()->user()->update(['api_token' => hash('sha256', $api_token)]);
return compact('api_token');
}
}
- 路由
Route::post('/register', 'Auth\ApiController@register');
Route::post('/login', 'Auth\ApiController@login');
Route::post('/refresh', 'Auth\ApiController@refresh');
Route::post('/logout', 'Auth\ApiController@logout');
- 过期时间
此时的token是没有过期时间的,永久存在
如果要让token有过期时间,就要在表中加入过期时间字段,然后新建一个中间件,在中间件里判断当前时间是否大于过期时间,如果大于的话,就认证失败。