Laravel 自带的 API 守卫驱动 token 使用详解
在Laravel
框架中,默认的用户认证守卫有两个,web
和api
,web
守卫默认的驱动是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 认证。
本文出于个人对默认配置的好奇进而探究之后写下的一片文章,也借鉴了网上的部分文章(忘记查看的文章地址了,也懒得找了),如有理解不到位或者错误的,请!斧!正!
欢迎任何形式转载,记得注明出处哦!
本作品采用《CC 协议》,转载必须注明作者和本文链接
如果想给aip_token设置有效期,该怎么让它过期呢
我还以为token是系统自动生成的呢,找半天。
可以
为什么说"api_token 简直就是裸奔"呢,这样用有什么不安全的地方吗?