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及更高版本,就不需要做任何事情。

解决方法

  1. 第一种方法:把MySQL升级为MySQL8版本,如果是生产环境,就建议你升级到MySQL8

  2. 第二种方法:就是在app/Providers/AppServiceProvider.php文件中,修改boot方法:

public function boot()
{
    Schema::defaultStringLength(191);//添加这行代码
}

第三步:生成秘钥

此命令会创建秘钥以用来生成安全的Access Token。除此之外,它也会创建用来生成Access Tokenpersonal access【私有授权】password grant【密码授权】

php artisan passport:install

我建议你直接使用上面的命令去生成

第四步:Passport 配置

config/auth.php 配置文件中,你应该设置 api 权限认证守卫的 driver 选项为 passport。当需要权限认证的 API 请求进来时会告诉你的应用去使用 Passport'sTokenGuard

'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 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 3

可以 github 下,多好好!

文章很不错!

3年前 评论

支持单点登录吗

1年前 评论

感觉不清不楚的,laravel9 user_info用不了

8个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!