Laravel 5.3 中使用 Laravel Passport 构建后端授权认证 API

Laravel在5.3中引入了新的官方OAuth扩展Laravel Passport,之前在5.1/5.2时一直是用dingo+jwt这一套来构建后端api,最近正好要构建新项目,想着试试官方这一个拓展如何。

安装

官方文档中有完整的安装调用过程,使用composer:

composer require laravel/passport

像使用其他组件一样,我们需要在config/app/phpproviders数组中注册Passport:

Laravel\Passport\PassportServiceProvider::class,

然后我们需要执行migrate创建相关的客户端数据表和令牌数据表,和passport:install来生成一些加密秘钥等,这些在官方文档中都有详细介绍。

构建认证函数

官方文档中针对如何简单使用做了初步介绍,下面我试着构造一个完整的客户端授权流程,首先创建ApiController

php artisen make:controller Apicontroller

ApiController中引入AuthenticatesUsers模块:

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

class ApiController extends Controller
{
    use  AuthenticatesUsers;

public function __construct()
{
    $this->middleware('api');
}

我们的客户端需要通过密码授权的方式来认证,我们需要在ApiController中重写AuthenticatesUsers部分功能函数来实现整个完整的授权流程,在这里我们调用Passport提供的oauth/token接口:

//调用认证接口获取授权码
protected function authenticateClient(Request $request)
{
    $credentials = $this->credentials($request);

    $data = $request->all();

    $request->request->add([
        'grant_type' => $data['grant_type'],
        'client_id' => $data['client_id'],
        'client_secret' => $data['client_secret'],
        'username' => $credentials['phone'],
        'password' => $credentials['password'],
        'scope' => ''
    ]);

    $proxy = Request::create(
        'oauth/token',
        'POST'
    );

    $response = \Route::dispatch($proxy);

    return $response;
}
//以下为重写部分
protected function authenticated(Request $request)
{
    return $this->authenticateClient($request);
}

protected function sendLoginResponse(Request $request)
{
    $this->clearLoginAttempts($request);

    return $this->authenticated($request);
}

protected function sendFailedLoginResponse(Request $request)
{
    $msg = $request['errors'];
    $code = $request['code'];
    return $this->failed($msg,$code);
}

在这里我们会遇到一个问题,官方文档中没有提及,就是我们要如何使用自定的用户名进行授权,找寻源码,其实在Laravel\Passport\Bridge\UserRepository.phpgetUserEntityByUserCredentials()函数中会看到这段代码:

if (method_exists($model, 'findForPassport')) {
    $user = (new $model)->findForPassport($username);
} else {
    $user = (new $model)->where('email', $username)->first();
}

我们只需要在我们配置在config/auth.php的模型中添加这段代码就可以完成自定义授权用户名,该方法和社区中另一个帖子方法一样:

 public function findForPassport($username) {
    return $this->where('phone', $username)->first();
}

这样一个授权流程基本完成,接下来创建一个Api/LoginController:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\ApiController;
use Illuminate\Http\Request;
use App\Models\User;                \\User.php我移动到了Models目录
use Validator;

class LoginController extends ApiController
{
    // 登录用户名标示为phone字段
    public function username()
    {
        return 'phone';
    }
    //登录接口,调用了ApiController中一些其他函数succeed\failed,上文未提及,用于接口格式化输出
    public function login(Request $request)
{
    $validator = Validator::make($request->all(), [
        'phone'    => 'required|exists:users',
        'password' => 'required|between:6,32',
        ]);

        if ($validator->fails()) {
        $request->request->add([
            'errors' => $validator->errors()->toArray(),
            'code' => 401,
            ]);                     
        return $this->sendFailedLoginResponse($request);
    }

    $credentials = $this->credentials($request);

    if ($this->guard('api')->attempt($credentials, $request->has('remember'))) {
        return $this->sendLoginResponse($request);
    }

    return $this->failed('login failed',401);
    }
}

构建路由

最后routes/api.php中加入我们需要的路由:

Route::group([
    'prefix'=>'/v1',
    'middleware' => ['api']
], function () {
    Route::post('/user/login','Api\LoginController@login');
});

测试

最后在postmen中调用接口:
image_1b2543doiq521a5c17lh1h3k14ve9.png-81.7kB

结果正确返回。

本帖已被设为精华帖!
本帖由 Summer 于 7年前 加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 15

比jwt好用吗???

7年前 评论

為什麼不直接使用 passport::route() ?

7年前 评论
Passport::routes(function (RouteRegistrar $router) {
     $router->forAccessTokens();
}, ['prefix' => 'api']);

例如這樣, 只開放 /oauth/token

7年前 评论

@jl9404 大神请教一下,前后端分离项目,前端每次登录请求/oauth/token都会创建一个新的access_token,怎样做判断,如果用户已有未过期的access_token 就直接返回该access_token 如果没有,再创建?

7年前 评论

@jl9404 想问一下只开发 /oauth/token来生成access token的时候,怎么实现多种方式的登录呢

7年前 评论

@Jinrenjie
Laravel passport 都是用 JWT 但這 cookie 好像會跟 session.lifetime 的設定
可能你需要加多一個中間件去擭取 JWT 內的 refresh token 拿新的 access token
你可以試試看
https://learnku.com/docs/laravel/5.3/passp...

@zhaohehe
只用 Passport::routes(); 就開放全部方式了

7年前 评论

@jl9404 你的意思是前端直接访问/oauth/token来拿到access token吗

7年前 评论

@jl9404 这个职能在本地项目里用吧,我想实现前后端完全分离 不同域名

7年前 评论

config/auth.php这个文件可以添加public方法???

7年前 评论

我一开始用passport的password模式来做登录授权,但后来加入第三方登录后,不知道怎么手动生成access_token和refresh_token,有哪位大神碰到相同的问题?急,在线等

7年前 评论

@jl9404 前端要隐藏client_id、client_secret 所以用自己的接口

7年前 评论
name001

客户端认证的,应该怎么配置,我password认证方式,正常,客户端认证的就返回500

6年前 评论

不通过账号密码登陆的该怎么整

6年前 评论

难道只有我一个人看不懂吗?这个不仍旧是用的SessionGard吗?好像和Passport没有关系啊,我看Taylor Otwell录的视频,他讲的意思好像是我们做后台接口,然后可以给很多人来调用到她们自己的网站,这里面每个网站来取数据的时候就需要用到api_token验证,我英语不太好,希望有人和我交流下,我可以把Laracast上购买的视频相赠。多谢了。

6年前 评论
张浩浩浩浩

@zhaohehe 通过博主的findForPassport方法改编

        if (is_numeric($username) && (strlen($username) == 11 && is_numeric($username))) {
            return $this->where('mobile', $username)->first();
        } else {
            return $this->where('name', $username)->first();
        }
5年前 评论

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