lumen验证解析和使用jwt做接口验证

好久没写 PHP 代码了,尤其是 Lumen,我是 Lumen 的忠实用户,自从面世开始,我就将 Lumen 作为我 API 的主要框架使用。

但说到 API,不得不说的一个概念:「前后端分离」,现在越来越多的团队都采用前后端分离,彻底解放出前端的优势,也让后台更加集中于数据的输出。关于这方面的讨论,不在这里讨论了,可以参考一些文章深入研究:

https://segmentfault.com/a/1190000006240370

正因为有了前后端分离,后台关注于接口 API 的输出,当时 Lumen 的出现,就是为 RESTful API 而生的:

6302-2c2d3495ea94d0a5.jpg

Decidedly Laravel. Delightfully Minimal.

Lightning fast micro-services and APIs delivered with the elegance you expect.

将 Lumen 作为接口框架使用,不得不解决一个核心问题:如何对访问者进行「认证」。

用户认证

Lumen 虽然与 Laravel 使用了相同的底层类库实现,但是因 Lumen 面向的是无状态 API 的开发,不支持 session,所以默认的配置不同。Lumen 必须使用无状态的机制来实现,如 API 令牌(Token)。

我们看看 Lumen 官网提供的例子:

use Illuminate\Http\Request;

$app->get('/post/{id}', ['middleware' => 'auth', function (Request $request, $id) {

$user = Auth::user();

$user = $request->user();

//

}]);

其中使用了中间件:'middleware' => 'auth',我们看看 auth 中间件函数:

$app->routeMiddleware([

'auth' => App\Http\Middleware\Authenticate::class,

]);

关联的是 Authenticate 类,我们看 Authenticate 的 handle 函数:

/**

* Handle an incoming request.

*

* @param \Illuminate\Http\Request $request

* @param \Closure $next

* @param string|null $guard

* @return mixed

*/

public function handle($request, Closure $next, $guard = null)

{

if ($this->auth->guard($guard)->guest()) {

return response('Unauthorized.', 401);

}

return $next($request);

}

首先会判断$this->auth->guard($guard)->guest()。我们继续跟进代码到 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 isset($this->guards[$name])

? $this->guards[$name]

: $this->guards[$name] = $this->resolve($name);

}

默认传入的 $name = null,所以我们看看 $this->getDefaultDriver()

/**

* Get the default authentication driver name.

*

* @return string

*/

public function getDefaultDriver()

{

return $this->app['config']['auth.defaults.guard'];

}

这就到了默认的配置 config 中了:

6302-e82f4fc242988f62.jpg

从 Lumen 源代码中可以看出 Lumen 的默认认证方式「api」。

我们再来看看 Laravel 的默认认证方式:

6302-0cbb0bd3263207dd.jpg

Laravel 默认采用「web」方式,而 web 方式是使用 session 来进行用户认证。这也就很好的说明了 Lumen 的无状态性。

接着我们需要明白 Lumen 如何通过「api」来进行用户认证的。

AuthServiceProvider 存放在 app/Providers 文件夹中,此文件中只有一个 Auth::viaRequest 调用。viaRequest 会在系统需要认证的时候被调用,此方法接受一个匿名函数传参,在此匿名函数内,你可以任意的解析 App\User 并返回,或者在解析失败时返回 null,如:

 
/**

* Boot the authentication services for the application.

*

* @return void

*/

public function boot()

{

// Here you may define how you wish users to be authenticated for your Lumen

// application. The callback which receives the incoming request instance

// should return either a User instance or null. You're free to obtain

// the User instance via an API token or any other method necessary.

$this->app['auth']->viaRequest('api', function ($request) {

if ($request->input('api_token')) {

return User::where('api_token', $request->input('api_token'))->first();

}

});

}

我们来看看 viaRequest 函数:

/**

* Register a new callback based request guard.

*

* @param string $driver

* @param callable $callback

* @return $this

*/

public function viaRequest($driver, callable $callback)

{

return $this->extend($driver, function () use ($callback) {

$guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());

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

return $guard;

});

}

这里关键的是 RequestGuard,这个类的核心函数:

/**

* 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;

}

return $this->user = call_user_func(

$this->callback, $this->request, $this->getProvider()

);

}

这个是判断是否获取用户信息,主要是调用callback 函数,而这个函数就是我们从 viaRequest 传入的:

function ($request) {

if ($request->input('api_token')) {

return User::where('api_token', $request->input('api_token'))->first();

}

}

而这只是举一个验证用户的例子,判断请求是否传入 api_token参数,并通过 User Model 直接匹配查找获取 User or null。

当然在实际开发中,我们不能只是简单的获取 api_token直接关联数据库查找用户信息。

在 API 开发中,用户认证是核心,是数据是否有保障的前提,目前主要有两种常用方式进行用户认证: JWT 和 OAuth2。

下一步

当前只是对 Lumen 的「用户认证」进行简单的了解,下一步通过对 「JWT」的学习,来看看如何利用 JWT 来有效的用户认证,更加安全的保障接口信息被有效的用户访问。

附:
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON 的开放标准 (RFC 7519)。该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密。

关于 JWT 更具体的介绍,相信网上有很多帖子和文章值得参考,这里先不阐述了。

为了学习 JWT 在 Lumen 中的使用,最好的办法就是在「程序员同志网 —— GitHub」搜索有关插件,找个 stars 最多的那个拿来研究研究。

6302-8f69587a95647335.jpg

image

tymondesigns/jwt-auth

JSON Web Token Authentication for Laravel & Lumen

6302-984fc20a149948e8.png

image

安装 jwt-auth

通过 Composer 安装:

composer require tymon/jwt-auth:"^1.0@dev"

6302-320b487f44890a8a.jpg

image

注: 0.5.* 版本未对 Lumen 专门做封装

将 $app->withFacades() 和 auth 认证相关的注释去掉:

<?php

require_once __DIR__.'/../vendor/autoload.php';

try {

(new Dotenv\Dotenv(__DIR__.'/../'))->load();

} catch (Dotenv\Exception\InvalidPathException $e) {

//

}

/*

|--------------------------------------------------------------------------

| Create The Application

|--------------------------------------------------------------------------

|

| Here we will load the environment and create the application instance

| that serves as the central piece of this framework. We'll use this

| application as an "IoC" container and router for this framework.

|

*/

$app = new Laravel\Lumen\Application(

realpath(__DIR__.'/../')

);

// 取消注释,这样就可以通过 Auth::user(),获取当前授权用户

$app->withFacades();

$app->withEloquent();

/*

|--------------------------------------------------------------------------

| Register Container Bindings

|--------------------------------------------------------------------------

|

| Now we will register a few bindings in the service container. We will

| register the exception handler and the console kernel. You may add

| your own bindings here if you like or you can make another file.

|

*/

$app->singleton(

Illuminate\Contracts\Debug\ExceptionHandler::class,

App\Exceptions\Handler::class

);

$app->singleton(

Illuminate\Contracts\Console\Kernel::class,

App\Console\Kernel::class

);

/*

|--------------------------------------------------------------------------

| Register Middleware

|--------------------------------------------------------------------------

|

| Next, we will register the middleware with the application. These can

| be global middleware that run before and after each request into a

| route or middleware that'll be assigned to some specific routes.

|

*/

// $app->middleware([

// App\Http\Middleware\ExampleMiddleware::class

// ]);

// 增加 auth 中间件

$app->routeMiddleware([

'auth' => App\Http\Middleware\Authenticate::class,

]);

/*

|--------------------------------------------------------------------------

| Register Service Providers

|--------------------------------------------------------------------------

|

| Here we will register all of the application's service providers which

| are used to bind services into the container. Service providers are

| totally optional, so you are not required to uncomment this line.

|

*/

$app->register(App\Providers\AppServiceProvider::class);

$app->register(App\Providers\AuthServiceProvider::class);

// $app->register(App\Providers\EventServiceProvider::class);

/*

|--------------------------------------------------------------------------

| Load The Application Routes

|--------------------------------------------------------------------------

|

| Next we will include the routes file so that they can all be added to

| the application. This will provide all of the URLs the application

| can respond to, as well as the controllers that may handle them.

|

*/

$app->router->group([

'namespace' => 'App\Http\Controllers',

], function ($router) {

require __DIR__.'/../routes/web.php';

});

return $app;

然后在 AppServiceProvider 中注册 LumenServiceProvider:

$this->app->register(\Tymon\JWTAuth\Providers\LumenServiceProvider::class);

在 Lumen 项目中,默认没有 config 文件夹,需要在项目根目录创建,并将 vendor 源代码中auth.php 复制出来,同时将 api 认证指定为「jwt」:

<?php

return [

/*

|--------------------------------------------------------------------------

| Authentication Defaults

|--------------------------------------------------------------------------

|

| This option controls the default authentication "guard" and password

| reset options for your application. You may change these defaults

| as required, but they're a perfect start for most applications.

|

*/

'defaults' => [

'guard' => env('AUTH_GUARD', 'api'),

],

/*

|--------------------------------------------------------------------------

| Authentication Guards

|--------------------------------------------------------------------------

|

| Next, you may define every authentication guard for your application.

| Of course, a great default configuration has been defined for you

| here which uses session storage and the Eloquent user provider.

|

| All authentication drivers have a user provider. This defines how the

| users are actually retrieved out of your database or other storage

| mechanisms used by this application to persist your user's data.

|

| Supported: "session", "token"

|

*/

'guards' => [

'api' => [

'driver' => 'jwt',

'provider' => 'users'

],

],

/*

|--------------------------------------------------------------------------

| User Providers

|--------------------------------------------------------------------------

|

| All authentication drivers have a user provider. This defines how the

| users are actually retrieved out of your database or other storage

| mechanisms used by this application to persist your user's data.

|

| If you have multiple user tables or models you may configure multiple

| sources which represent each model / table. These sources may then

| be assigned to any extra authentication guards you have defined.

|

| Supported: "database", "eloquent"

|

*/

'providers' => [

'users' => [

'driver' => 'eloquent',

'model' => \App\User::class,

],

],

/*

|--------------------------------------------------------------------------

| Resetting Passwords

|--------------------------------------------------------------------------

|

| Here you may set the options for resetting passwords including the view

| that is your password reset e-mail. You may also set the name of the

| table that maintains all of the reset tokens for your application.

|

| You may specify multiple password reset configurations if you have more

| than one user table or model in the application and you want to have

| separate password reset settings based on the specific user types.

|

| The expire time is the number of minutes that the reset token should be

| considered valid. This security feature keeps tokens short-lived so

| they have less time to be guessed. You may change this as needed.

|

*/

'passwords' => [

//

],

];

最后,因为 JWT 协议需要用到 secret,所以需要生成一个 secret:

php artisan jwt:secret

6302-e14f9aa6bb083d08.jpg

image

使用 jwt-auth

1. 更新 User Model

继承 Tymon\JWTAuth\Contracts\JWTSubject:

<?php

namespace App;

use Illuminate\Auth\Authenticatable;

use Laravel\Lumen\Auth\Authorizable;

use Illuminate\Database\Eloquent\Model;

use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;

use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;

use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject

{

use Authenticatable, Authorizable;

/**

* The attributes that are mass assignable.

*

* @var array

*/

protected $fillable = [

'name', 'email',

];

/**

* The attributes excluded from the model's JSON form.

*

* @var array

*/

protected $hidden = [

'password',

];

/**

* Get the identifier that will be stored in the subject claim of the JWT.

*

* @return mixed

*/

public function getJWTIdentifier()

{

return $this->getKey();

}

/**

* Return a key value array, containing any custom claims to be added to the JWT.

*

* @return array

*/

public function getJWTCustomClaims()

{

return [];

}

}

2. 写一个 Login 方法,验证登陆信息,并返回 token 回客户端:

// 路由

$router->post('/auth/login', 'AuthController@postLogin');

postLogin 方法:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use Tymon\JWTAuth\JWTAuth;

class AuthController extends Controller

{

protected $jwt;

public function __construct(JWTAuth $jwt)

{

$this->jwt = $jwt;

}

public function postLogin(Request $request)

{

if (! $token = $this->jwt->attempt($request->only('email', 'password'))) {

return response()->json(['user_not_found'], 404);

}

return response()->json(compact('token'));

}

}

可以请求试试了,用 Postman 跑跑:

6302-245e2a148a9ea91b.jpg

image

有了 token 了。我们就可以用来测试,看能不能认证成功,获取用户信息。

3. 使用 token 获取用户信息

// 使用 auth:api 中间件

$router->group(['middleware' => 'auth:api'], function($router)

{

$router->get('/test', 'ExampleController@getUser');

});

只要验证通过,就可以利用 Auth:user()方法获取用户信息了。

public function getUser(Request $request) {

return response()->json(['user' => Auth::user()]);

}

6302-dbc6a7288c90740d.jpg

image

对照数据库:

6302-6ef3747ff32fe3a7.jpg

image

以后只要在请求的 headers 中加入 token 信息即可,完美实现用户认证。

想了解有关 Lumen 的认证相关内容,可以参考上一篇文章《学习 Lumen 用户认证 (一)》https://mp.weixin.qq.com/s/KVUQE2DUetNB2kqxHs0VDg

也可以参考 Lumen 官网
https://lumen.laravel-china.org/docs/5.3/authentication

总结

对获取到 token 值 (eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vZGVtby5hcHAvYXV0aC9sb2dpbiIsImlhdCI6MTUxMDQ3NTQ5MiwiZXhwIjoxNTEwNDc5MDkyLCJuYmYiOjE1MTA0NzU0OTIsImp0aSI6Imx3UFpSMTN0MlV5eXRib1oiLCJzdWIiOjEsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ.YTvsiO9MT3VgPZiI03v2sVEIsGLj8AUwJiDuXvCAvHI) 仔细观察,就会发现中间是由两个「.」来合并三段信息的。

下一步我们就来研究研究 JWT 的原理和也可以自己动手写个基于 JWT 的 Lumen 认证插件出来。

猜你喜欢

转载自blog.csdn.net/daily886/article/details/81386668