學習 Lumen 使用者認證 (二) —— 使用 jwt-auth 外掛

coding01發表於2019-02-16

通過上一篇《學習 Lumen 使用者認證 (一)》https://mp.weixin.qq.com/s/KVUQE2DUetNB2kqxHs0VDg的學習,大致懂了 Lumen 的使用者認證主要使用 「api」的方式,來預設進行使用者認證:

<?php

namespace AppProviders;

use AppUser;
use IlluminateSupportFacadesGate;
use IlluminateSupportServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * 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();
            }
        });
    }
}

當然在實際開發中,我們不能只是簡單的獲取 api_token直接關聯資料庫查詢使用者資訊。

在 API 開發中,使用者認證是核心,是資料是否有保障的前提,目前主要有兩種常用方式進行使用者認證: JWT 和 OAuth2。

本文將簡要說說如何利用 JWT 來進行使用者認證

JWT

Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基於JSON 的開放標準 (RFC 7519)。該 token 被設計為緊湊且安全的,特別適用於分散式站點的單點登入(SSO)場景。JWT 的宣告一般被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊,該 token 也可直接被用於認證,也可被加密。

關於 JWT 更具體的介紹,相信網上有很多帖子和文章值得參考,這裡先不闡述了。

為了學習 JWT 在 Lumen 中的使用,最好的辦法就是在「程式設計師同志網 —— GitHub」搜尋有關外掛,找個 stars 最多的那個拿來研究研究。

tymondesigns/jwt-auth

JSON Web Token Authentication for Laravel & Lumen

安裝 jwt-auth

通過 Composer 安裝:

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

注: 0.5.* 版本未對 Lumen 專門做封裝

將 $app->withFacades() 和 auth 認證相關的註釋去掉:

<?php

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

try {
    (new DotenvDotenv(__DIR__.`/../`))->load();
} catch (DotenvExceptionInvalidPathException $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 LaravelLumenApplication(
    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(
    IlluminateContractsDebugExceptionHandler::class,
    AppExceptionsHandler::class
);

$app->singleton(
    IlluminateContractsConsoleKernel::class,
    AppConsoleKernel::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([
//    AppHttpMiddlewareExampleMiddleware::class
// ]);

// 增加 auth 中介軟體
$app->routeMiddleware([
    `auth` => AppHttpMiddlewareAuthenticate::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(AppProvidersAppServiceProvider::class);
$app->register(AppProvidersAuthServiceProvider::class);
// $app->register(AppProvidersEventServiceProvider::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` => `AppHttpControllers`,
], function ($router) {
    require __DIR__.`/../routes/web.php`;
});

return $app;

然後在 AppServiceProvider 中註冊 LumenServiceProvider:

$this->app->register(TymonJWTAuthProvidersLumenServiceProvider::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`  => AppUser::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

使用 jwt-auth

1. 更新 User Model

繼承 TymonJWTAuthContractsJWTSubject:

<?php

namespace App;

use IlluminateAuthAuthenticatable;
use LaravelLumenAuthAuthorizable;
use IlluminateDatabaseEloquentModel;
use IlluminateContractsAuthAuthenticatable as AuthenticatableContract;
use IlluminateContractsAuthAccessAuthorizable as AuthorizableContract;
use TymonJWTAuthContractsJWTSubject;

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

use IlluminateHttpRequest;
use TymonJWTAuthJWTAuth;

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 跑跑:

有了 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()]);
    }

對照資料庫:

以後只要在請求的 headers 中加入 token 資訊即可,完美實現使用者認證。

想了解有關 Lumen 的認證相關內容,可以參考上一篇文章《學習 Lumen 使用者認證 (一)》https://mp.weixin.qq.com/s/KVUQE2DUetNB2kqxHs0VDg

也可以參考 Lumen 官網
https://lumen.laravel-china.o…

總結

對獲取到 token 值 (eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vZGVtby5hcHAvYXV0aC9sb2dpbiIsImlhdCI6MTUxMDQ3NTQ5MiwiZXhwIjoxNTEwNDc5MDkyLCJuYmYiOjE1MTA0NzU0OTIsImp0aSI6Imx3UFpSMTN0MlV5eXRib1oiLCJzdWIiOjEsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ.YTvsiO9MT3VgPZiI03v2sVEIsGLj8AUwJiDuXvCAvHI) 仔細觀察,就會發現中間是由兩個「.」來合併三段資訊的。

下一步我們就來研究研究 JWT 的原理和也可以自己動手寫個基於 JWT 的 Lumen 認證外掛出來。

「未完待續」


coding01 期待您繼續關注

qrcode


也很感謝您能看到這了

qrcode

相關文章