Laravel 使用 passport 开发 API 接口
该文章转载自我的博客:我的博客
在现在的情境下,前后端分离是越来越普遍了,而laravel本身就对API有良好的支持,所以我在这里记录一下我使用laravel的官方扩展包passport
去做API开发。
本文的laravel版本是laravel 6.*
,当然你也可以使用laravel的其他版本。
如果你使用 laravel 5.4
或者 更低版本
,你需要在 config/app.php
文件中为Passport注册服务。就这样,在这个文件中的providers数组中添加注册服务。
本文只是作为一个参考,告诉你如何使用passport。实际开发按照你自己的来。
第一步:安装
通过Composer去安装
composer require laravel/passport
第二步:执行数据库迁移
php artisan migrate
注意:
这里的迁移命令会把所有的迁移文件都执行,所以如果你只想迁移passport
的文件,在vendor/laravel/passport/database/migrations
目录里面,是与passport
有关的迁移文件。
执行下面命令:
php artisan migrate --path=./vendor/laravel/passport/database/migrations/
如果迁移的时候报这种类型的错
Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
原因
Laravel 5.4对默认数据库字符集进行了更改,现在utf8mb4它包含了对存储表情符号的支持。这只会影响新的应用程序,只要您运行MySQL v5.7.7及更高版本,就不需要做任何事情。
解决方法
第一种方法:把MySQL升级为MySQL8版本,如果是生产环境,就建议你升级到
MySQL8
第二种方法:就是在
app/Providers/AppServiceProvider.php
文件中,修改boot方法:
public function boot()
{
Schema::defaultStringLength(191);//添加这行代码
}
第三步:生成秘钥
此命令会创建秘钥以用来生成安全的Access Token
。除此之外,它也会创建用来生成Access Token
的 personal access【私有授权】
和 password grant【密码授权】
:
php artisan passport:install
我建议你直接使用上面的命令去生成
第四步:Passport 配置
在 config/auth.php
配置文件中,你应该设置 api
权限认证守卫的 driver
选项为 passport
。当需要权限认证的 API
请求进来时会告诉你的应用去使用 Passport's
的 TokenGuard
。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
将 Laravel\Passport\HasApiTokens
trait 添加到你的 App\User
模型中。这个 trait 会为模型添加一系列助手函数用来验证用户的秘钥和作用域:
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
}
如果你的不是App\User
这个表,在config/auth.php
配置文件中修改:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class, //这里修改成你的模型
],
],
接下来,你应该在 AuthServiceProvider
中的 boot 方法中调用 Passport::routes
方法。这个方法会注册必要的路由去颁发访问令牌,撤销访问令牌,客户端和个人令牌:
<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
由于passport生成的令牌默认是长期有效的,所以我们把过期时间设置为24小时:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addDays(1));//令牌过期时间
Passport::refreshTokensExpireIn(Carbon::now()->addDays(1));//刷新令牌过期时间
Passport::personalAccessTokensExpireIn(Carbon::now()->addDays(1));//个人访问令牌过期时间
}
}
然后Passport
的配置告一段落。接下来就是测试是否有用。
第五步:创建模型、控制器和路由
创建模型
php artisan make:model Models/User
模型代码:
<?php
namespace App\Models;
use App\Helper\Utils;
use Illuminate\Database\Eloquent\Model;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
//注意,这里继承的是Authenticatable而不是Model,否则会出现findForPassport,validateForPassportPasswordGrant不存在的报错
class User extends Authenticatable
{
use HasApiTokens,Notifiable;//Notifiable这个可有可无
protected $table="users";
public function findForPassport($account)
{
//token验证默认是email,我改成了account
return $this->where('account', $account)->first();
}
public function validateForPassportPasswordGrant($password)
{
//这里使用的是自定义的密码验证
$md5_password=Utils::GetMd5Password($password,$this->salt);
return Utils::CheckHashPassword($md5_password,$this->password);
}
}
创建路由
在route/api.php
文件下新增:
Route::prefix('v1')->namespace('Api')->group(function (){
Route::post("/register","UserController@register");
Route::post("/login","UserController@Login");
//这里使用passport中间件验证token
Route::middleware('auth:api')->group(function (){
Route::post("/user_info","UserController@UserInfo");
Route::post("/logout","UserController@Logout");
});
});
创建控制器
php artisan make:controller Api/UserController
控制器代码
<?php
namespace App\Http\Controllers\Api;
use App\Helper\Utils;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Validator;
class UserController extends Controller
{
//注册
public function register(Request $request){
$post_data=$request->post();
$rule=[
"name"=>"required",
"account"=>"required",
"password"=>"required",
"phone"=>"required",
];
$message=[
"required"=>":attribute 不能为空"
];
$attr=[
"name"=>"用户名称",
"account"=>"账号",
"password"=>"密码",
"phone"=>"手机号",
];
$validator = Validator::make($post_data,$rule,$message,$attr);
if($validator->fails())
{
return Utils::JsonData(400,$validator->errors()->first());
}
//验证用户是否已存在
$is_user=User::query()->where("account",trim($post_data["account"]))->count();
if($is_user > 0){
return Utils::JsonData(400,"用户已存在");
}
$salt=Utils::GetRandomString();
$password=Utils::GetHashPassword($post_data["password"],$salt);
$data_info=[
"name"=>trim($post_data["name"]),
"account"=>trim($post_data["account"]),
"password"=>$password,
"salt"=>$salt,
"phone"=>$post_data["phone"],
"create_time"=>date("Y-m-d H:s:i",time()),
"update_time"=>date("Y-m-d H:s:i",time()),
];
try{
User::query()->insert($data_info);
return Utils::JsonData(200,"注册成功");
}catch (\Exception $e){
return Utils::JsonData(400,"注册失败");
}
}
//登录
public function Login(Request $request){
$post_data=$request->post();
//验证用户是否存在
$user=User::query()->where("account",$post_data["account"])->first();
if(!isset($user->id)){
return Utils::JsonData(400,"用户不存在");
}
//验证密码
$md5_password=Utils::GetMd5Password($post_data["password"],$user->salt);
if(!Utils::CheckHashPassword($md5_password,$user->password)){
return Utils::JsonData(400,"密码错误");
}
//生成token
$tokenResult = $user->createToken('LaravelApi');
$token = $tokenResult->token;
if ($post_data["remember_me"]) {
$token->expires_at = Carbon::now()->addWeeks(1);//设置令牌过期时间为一周
}
$token->save();//保存令牌
$data_info=[
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse(
$tokenResult->token->expires_at
)->toDateTimeString()
];
return Utils::JsonData(200,"请求成功",$data_info);
}
//退出
public function Logout(Request $request){
$request->user()->token()->revoke();
return Utils::JsonData(200,"退出成功");
}
//获取用户信息
public function UserInfo(Request $request){
$user=$request->user();
return Utils::JsonData(200,"请求成功",$user);
}
}
创建辅助函数类 Utils
在app/
创建个Helper/Utils.php
,代码如下:
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2020/5/25
* Time: 10:24
*/
namespace App\Helper;
use Illuminate\Support\Facades\Hash;
class Utils
{
/**
* 请求返回数据封装
* @param int $code 状态码 200:成功,400:错误,300:未授权
* @param string $msg
* @param null $data
*/
public static function JsonData($code=200,$msg="",$data=null,$is_return=true){
$return_data=array(
"code"=>$code,
"msg"=>$msg,
"data"=>$data
);
if($is_return){
return json_encode($return_data);
}else{
echo json_encode($return_data);
}
}
/**
* 生成哈希密码
* @param $password //密码
* @param $salt //盐值
*/
public static function GetHashPassword($password,$salt){
//加密规则,先生成MD5的加密密码,然后再做哈希处理
$md5_password=self::GetMd5Password($password,$salt);
//该方法还有一个参数,可以调整工作因子等配置,当然你也可以在config/hashing.php配置文件中更改,参考官方文档
$hash_password=Hash::make($md5_password);
return $hash_password;
}
/**
* 生成MD5的加密密码
* @param $password //密码
* @param $salt
*/
public static function GetMd5Password($password,$salt){
$md5_password=md5(md5($password.$salt).$salt);
return $md5_password;
}
/**
* 验证密码是否正确
* @param $md5_password
* @param $hash_password
*/
public static function CheckHashPassword($md5_password,$hash_password){
if(Hash::check($md5_password,$hash_password)){
return true;
}else{
return false;
}
}
/**
* 检查hash是否需要更新
* @param $hash_pass
*/
public static function InspectHashOverdue($hash_password){
if(Hash::needsRehash($hash_password)){
return true;
}else{
return false;
}
}
/**
* 生成随机的字符串
* @param int $len
* @param bool $special
*/
public static function GetRandomString($len=4,$special=false){
if($special){
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|";
}else{
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
$str="";
for ($i=0;$i < $len;$i++){
// 这里提供两种字符获取方式
// 第一种是使用 substr 截取$chars中的任意一位字符;
// 第二种是取字符数组 $chars 的任意元素
// $str .= substr($chars, mt_rand(0, strlen($chars) – 1), 1);
$str .= $chars[ mt_rand(0, strlen($chars) - 1) ];
}
return $str;
}
}
使用postman测试接口
注意:请求的时候一定要把header
头的Accept
设置为application/json
注册:
登录:
获取用户信息
第六步:自定义异常捕获返回
上一步已经把这些方法完成,如果你有把上面的接口执行一遍,你就会发现,我token如果没有验证通过,会抛出一个异常,而不是我们想要的json格式,所以我们要捕获到异常,然后返回我们需要的json格式。
在app/Helper/
下创建异常捕获类ExceptionReport.php
代码如下:
<?php
namespace App\Helper;
use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
/**
* 自定义异常捕获
* Class ExceptionReport
* @package App\Api\Helpers
*/
class ExceptionReport
{
/**
* @var Exception
*/
public $exception;
/**
* @var Request
*/
public $request;
/**
* @var
*/
protected $report;
/**
* ExceptionReport constructor.
* @param Request $request
* @param Exception $exception
*/
function __construct(Request $request, Exception $exception)
{
$this->request = $request;
$this->exception = $exception;
}
/**
* @var array
*/
public $doReport = [
AuthenticationException::class => ['未授权',401],
ModelNotFoundException::class => ['该模型未找到',404],
AuthorizationException::class => ['没有此权限',403],
ValidationException::class => [],
UnauthorizedHttpException::class=>['未登录或登录状态失效',422],
NotFoundHttpException::class=>['没有找到该页面',404],
MethodNotAllowedHttpException::class=>['访问方式不正确',405],
QueryException::class=>['参数错误',401],
];
/**
* @return bool
*/
public function shouldReturn(){
foreach (array_keys($this->doReport) as $report){
if ($this->exception instanceof $report){
$this->report = $report;
return true;
}
}
return false;
}
/**
* @param Exception $e
* @return static
*/
public static function make(Exception $e){
return new static(\request(),$e);
}
/**
* @return mixed
*/
public function report(){
//这个是表单验证的异常
if ($this->exception instanceof ValidationException){
$error = current($this->exception->errors());
return Utils::JsonData(400,current($error));
}
$message = $this->doReport[$this->report];
return Utils::JsonData(400,$message[0]);
}
}
然后在app/Exceptions/Handler.php
中的render
方法拦截异常
public function render($request, Exception $exception)
{
// 将方法拦截到自己的ExceptionReport
$reporter = ExceptionReport::make($exception);
if ($reporter->shouldReturn()){
return $reporter->report();
}
return parent::render($request, $exception);
}
然后验证不通过就不会抛出一个异常了,而是返回json格式的错误。
本作品采用《CC 协议》,转载必须注明作者和本文链接
可以 github 下,多好好!
文章很不错!
支持单点登录吗
感觉不清不楚的,laravel9 user_info用不了