Laravel 自带的 api 守卫驱动 token 使用详解

Laravel框架中,默认的用户认证守卫有两个,webapiweb守卫默认的驱动是session,而api守卫默认的驱动是token。那么,该如何使用这个token驱动?

寻找 TokenGuard

通过 Auth::guard() 这个方法,可以追溯到 token 驱动对应的类。来看Illuminate\Auth\AuthManager中的代码:

 /**
     * Attempt to get the guard from the local cache.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    /**
     * Resolve the given guard.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth driver [{$config['driver']}] for guard [{$name}] is not defined.");
    }

可以看到,默认情况下就会调用到 createTokenDriver 。来看看这个方法:

 public function createTokenDriver($name, $config)
 {
     // The token guard implements a basic API token based guard implementation
     // that takes an API token field from the request and matches it to the
     // user in the database or another persistence layer where users are.
     $guard = new TokenGuard(
         $this->createUserProvider($config['provider'] ?? null),
         $this->app['request']
     );

     $this->app->refresh('request', $guard, 'setRequest');

     return $guard;
 }

显然,api守卫默认的驱动就是TokenGuard

解读 TokenGuard

/**
     * Create a new authentication guard.
     *
     * @param  \Illuminate\Contracts\Auth\UserProvider  $provider
     * @param  \Illuminate\Http\Request  $request
     * @param  string  $inputKey
     * @param  string  $storageKey
     * @return void
     */
    public function __construct(UserProvider $provider, Request $request, $inputKey = 'api_token', $storageKey = 'api_token')
    {
        $this->request = $request;
        $this->provider = $provider;
        $this->inputKey = $inputKey;
        $this->storageKey = $storageKey;
    }

    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    {
        // If we've already retrieved the user for the current request we can just
        // return it back immediately. We do not want to fetch the user data on
        // every call to this method because that would be tremendously slow.
        if (! is_null($this->user)) {
            return $this->user;
        }

        $user = null;

        $token = $this->getTokenForRequest();

        if (! empty($token)) {
            $user = $this->provider->retrieveByCredentials(
                [$this->storageKey => $token]
            );
        }

        return $this->user = $user;
    }

从构造函数和 user() 方法中可以看出,默认使用

['api_token' => $token]

这个数组去获取用户,也就是说,在用户表中我们需要一个字段(默认api_token)去存储标识用户的 token。

开始使用 token 进行api认证

添加数据表字段

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddUsersApiTokenField extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('api_token', 60)->unique()->nullable()->after('password');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('api_token');
        });
    }
}

创建登录控制器

这里不演示注册之类的,假设我们的 users 表中已经存在用户,先创建一个用于 api 登录的控制器。在每次登录的时候,更新一次用户的 api_token 。这里使用了 ThrottlesLogins ,用来控制登录的尝试次数。

<?php

namespace App\Http\Controllers\Api;

use Hash;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Validation\ValidationException;

class LoginController extends Controller
{
    use ThrottlesLogins;

    /**
     * @param Request $request
     * @return \Illuminate\Http\Response|void
     * @throws ValidationException
     */
    public function login(Request $request)
    {
        $this->validateLogin($request);

        if ($this->hasTooManyLoginAttempts($request)) {
            return $this->sendLockoutResponse($request);
        }

        return $this->attempLogin($request);

    }

    /**
     * @param Request $request
     */
    public function validateLogin(Request $request)
    {
        $this->validate($request, [
            $this->username() => 'required|string',
            'password' => 'required|string'
        ]);
    }

    /**
     * @param Request $request
     * @return \Illuminate\Http\Response|void
     */
    protected function attempLogin(Request $request)
    {
        $this->incrementLoginAttempts($request);

        $user = User::where('email', $request->email)->first();

        if (!$user || !Hash::check($request->password, $user->password)) {
            return $this->sendFailedLoginResponse($request);
        }

        // 更新 api_key
        $api_token = uniqid($user->id);
        $user->api_token = $api_token;
        $user->save();

        return $this->sendLoginResponse($request, $user);
    }

    /**
     * @param Request $request
     */
    protected function sendFailedLoginResponse(Request $request)
    {
        throw ValidationException::withMessages([
            $this->username() => [trans('auth.failed')],
        ]);
    }

    /**
     * @param Request $request
     * @param User $user
     * @return \Illuminate\Http\Response
     */
    protected function sendLoginResponse(Request $request, User $user)
    {
        $this->clearLoginAttempts($request);

        return \Response::make([
            'user' => $user,
            'token' => $user->api_token
        ]);
    }

    public function username()
    {
        return 'email';
    }
}

添加登录路由

routes\api.php 修改如下:

Route::namespace('Api')->group(function () {
    Route::post('login', 'LoginController@login');
}); # 登录路由

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

调试

测试之前先往 users 表中添加几个用户,以下是我的测试数据。


可以看到登录成功并且返回了 token 。
接下去我们使用获取到的 token 请求需要登录的接口,默认有一个,就是/user.

ok~ 已经成功返回了数据,说明登录成功了!

例子中,api_token 是通过 OAuth 2.0 Access Token 的形式传进去的,但这不是唯一的方法,可以查看 TokenGuard 中的 getTokenForRequest 这个方法,它告诉我们可以用四种不同的形式传入 api_token

总结

默认的 api token 认证虽然在文档中没有提及如何使用,但是通过查看代码,也很容易使用。但是,在我们不重写或者扩展 tokenGUard的情况下,api_token 简直就是裸奔,显然不可能就这样应用到项目中去。个人猜测,框架中提供这个功能是为了让我们更好的理解 api 认证的工作原理,方便我们开发自己需要的 guard ,而且官方文档也推荐我们使用 passport 或者 jwt 进行 api 认证。

本文出于个人对默认配置的好奇进而探究之后写下的一片文章,也借鉴了网上的部分文章(忘记查看的文章地址了,也懒得找了),如有理解不到位或者错误的,请!斧!正!

欢迎任何形式转载,记得注明出处哦!

Tags:, ,

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注