Lumen 8.0 使用 Jwt 認證的 Api

charliecen發表於2020-09-18

Lumen 8.0 使用 Jwt 認證的 Api

原始碼參考

安裝lumen

composer create-project --prefer-dist laravel/lumen blog

拷貝配置

copy .env.example .env

啟動

php -S localhost:83 -t public

生成APP_KEY

由於lumenkey:generate命令,所以需要建立檔案

<?php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Symfony\Component\Console\Input\InputOption;

class KeyGenerateCommand extends Command
{
    // 命令名
    protected $name = 'key:generate';
    // 描述
    protected $description = "Set the application key";
    // 執行
    public function handle() {
        $key = $this->getRandomKey();
        if ($this->option('show')){
            $this->line('<command>'.$key.'</command>');
            return;
        }
        $path = base_path('.env');
        if (file_exists($path)){
            file_put_contents($path, str_replace('APP_KEY='.env('APP_KEY'), 'APP_KEY='.$key, file_get_contents($path)));
        }
        $this->info("Application key [$key] set successfully.");
    }

    /**
     * @return string
     * 獲取隨機32key
     */
    protected function getRandomKey(){
        return Str::random(32);
    }

    protected function getOptions()
    {
        return [
            ['show', null, InputOption::VALUE_NONE, 'Simply display the key instead of modifying files.']
        ];
    }
}

新增命令到app/Console/Kernel.php檔案中

<?php

namespace App\Console;

use App\Console\Commands\KeyGenerateCommand;
use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        KeyGenerateCommand::class
    ];
}

生成key

D:\phpstudy_pro\WWW\lumen>php artisan key:generate
Application key [wRtxrX9DZs8HlZ0WMERxHo43Mkjbev1l] set successfully.

修改配置

APP_NAME=Lumen
APP_ENV=local
APP_KEY=wRtxrX9DZs8HlZ0WMERxHo43Mkjbev1l
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=Asia/Shanghai

LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=lumen
DB_USERNAME=root
DB_PASSWORD=phpTest1!

CACHE_DRIVER=file
QUEUE_CONNECTION=sync

安裝jwt

composer require tymon/jwt-auth

配置啟動檔案

修改boostrap/app.php

<?php

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

(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
    dirname(__DIR__)
))->bootstrap();

date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));

$app = new Laravel\Lumen\Application(
    dirname(__DIR__)
);

$app->withFacades();

$app->withEloquent();

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->configure('app');

// 認證中介軟體
 $app->routeMiddleware([
     'auth' => App\Http\Middleware\Authenticate::class,
 ]);

// $app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\AuthServiceProvider::class);
// $app->register(App\Providers\EventServiceProvider::class);
// jwt註冊
$app->register(\Tymon\JWTAuth\Providers\LumenServiceProvider::class);

$app->router->group([
    'namespace' => 'App\Http\Controllers',
], function ($router) {
    require __DIR__.'/../routes/api.php';
    require __DIR__.'/../routes/web.php';
});

return $app;

生成祕鑰

php artisan jwt:secret

# 檢視.env
JWT_SECRET=JGPUooBjSd2PI91nizCI10ZfI56yLsl9dg9GiUozf8pgjfOS9fPYeTpgVIJaSbBG

自定義響應格式

載入此檔案,需要在composer.json新增一下程式碼

"autoload": {
        ...
        "files": [
            "app/Helpers/helper.php"
        ]
    },
<?php

/**
 * 響應格式
 */
if (!function_exists('resp')) {
    function resp($code = 200, $msg = '', $data = []) {
        return response()->json([
            'code'  => $code,
            'msg'   => $msg,
            'data'  => $data,
        ]);
    }
}

使用者認證

使用make:migration 生成表

預設此檔案已存在,如果不存在則建立

D:\phpstudy_pro\WWW\lumen>php artisan make:migration create_users_table --create=users
Created Migration: 2020_09_18_103745_create_users_table

定義表欄位

<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('username');
            $table->string('password');
            $table->string('email');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

資料遷移

D:\phpstudy_pro\WWW\lumen>php artisan migrate
Migrating: 2020_09_18_103745_create_users_table
Migrated:  2020_09_18_103745_create_users_table (280.89ms)
修改User模型
<?php

namespace App\Models;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject
{
    use Authenticatable, Authorizable, HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'username', 'email', 'password'
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];

    /**
     * @inheritDoc
     * 獲取jwt中的使用者標識
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * @inheritDoc
     * 獲取jwt中的使用者自定義欄位
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}
配置認證guard

拷貝vendor/laravel/lumen-framework/config/auth.phpconfig/目錄下,並修改如下:

<?php

return [

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'api')
    ],

    'guards' => [
        'api'   =>  [
            'driver' => 'jwt',
            'provider' => 'users'
        ]
    ],

    'providers' => [
        'users' => [
            'driver'    => 'eloquent',
            'model'     => \App\Models\User::class
        ]
    ],

    'passwords' => [
        //
    ],

];
定義路由檔案api.php
<?php
/** @var \Laravel\Lumen\Routing\Router $router */

// 建立使用者和登入
$router->group([
    'prefix' => 'auth',
    'namespace' => 'Api',
], function () use ($router) {
    $router->post('/store', 'UserController@store');
    $router->post('/login', 'UserController@login');
    $router->get('/user', 'UserController@index');
    $router->get('/me', 'UserController@me');
    $router->get('/logout', 'UserController@logout');
});
建立UserController
<?php
namespace App\Http\Controllers\Api;

use App\Exceptions\Code;
use App\Exceptions\Msg;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\Controller;

class UserController extends Controller
{

    /**
     * UserController constructor.
     * 認證中介軟體,排除登入和註冊
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login','store']]);
    }

    /**
     * @return \Illuminate\Http\JsonResponse
     * 所有使用者
     */
    public function index() {
        $users = User::paginate(env('PAGINATE'));
        return resp(Code::Success, Msg::Success, $users);
    }

    /**
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     * 登入
     */
    public function login(Request $request) {
        $message = [
            'username.required' => "請輸入使用者名稱",
            'password.required' => "請輸入密碼",
        ];
        $validator = Validator::make($request->all(), [
            'username' => 'required',
            'password'  => 'required'
        ], $message);
        if ($validator->fails()) {
            foreach($validator->errors()->getMessages() as $error) {
                return resp(Code::LoginFailed, $error[0]);
            }
        }
        $credentials = request(['username', 'password']);
        if (! $token = auth()->attempt($credentials)) {
            return resp(Code::LoginFailed, Msg::LoginFailed);
        }
        return resp(Code::LoginSuccess, Msg::LoginSuccess, $this->responseWithToken($token));
    }

    /**
     * @param $token
     * @return array
     * 返回token資訊
     */
    protected function responseWithToken($token) {
        return [
            'access_token' => $token,
            'token_type'    => 'bearer',
            'expires_in'    => auth()->factory()->getTTL() * env('JWT_TTL')
        ];
    }

    /**
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     * 建立使用者
     */
    public function store(Request $request)
    {
        $message = [
            'email.required'    => '請輸入郵箱',
            'email.email'       => '郵箱格式不正確',
            'email.unique'      => '郵箱已存在',
            'username.required' => '請輸入使用者名稱',
            'password.required' => '請輸入密碼',
            'username.min'      => '使用者名稱至少 :min 位',
            'password.min'      => '密碼至少 :min 位',
        ];
        $validator = Validator::make($request->input(), [
            'email' => 'required|email|unique:users',
            'username' => 'required|min:6',
            'password' => 'required|min:8',
        ], $message);

        if ($validator->fails()) {
            foreach($validator->errors()->getMessages() as $error) {
                return resp(Code::CreateUserFailed, $error[0]);
            }
        }
        $username = $request->get('username');
        $email = $request->get('email');
        $password = $request->get('password');

        $attributes = [
            'email' => $email,
            'username' => $username,
            'password' => app('hash')->make($password)
        ];
        $user = User::create($attributes);
        return resp(Code::CreateUserSuccess, Msg::CreateUserSuccess, $user);
    }

    /**
     * @return \Illuminate\Http\JsonResponse
     * 當前使用者資訊
     */
    public function me() {
        return resp(Code::UserIsMe, Msg::UserIsMe, auth()->user());
    }

    /**
     * @return \Illuminate\Http\JsonResponse
     * 退出登入
     */
    public function logout() {
        auth()->logout();
        return resp(Code::LoginOutSuccess, Msg::LoginOutSuccess);
    }

    /**
     * @return array
     * 重新整理token
     */
    public function refresh(){
        return $this->responseWithToken(auth()->refresh());
    }
}
定義返回狀態碼和資訊
<?php


namespace App\Exceptions;


class Code
{
    const   Success           = 200;
    const   Unauthorized      = 401;
    const   Failed            = 500;

    const   CreateUserSuccess = 10001;
    const   CreateUserFailed  = 10002;

    const   LoginSuccess      = 20001;
    const   LoginFailed       = 20002;
    const   LoginOutSuccess   = 20003;
    const   UserIsMe          = 20004;

    const   CreatePostsSuccess  =   30001;
    const   CreatePostsFailed   =   30002;
    const   PostsListSuccess    =   30003;
    const   PostsListFailed     =   30004;
}
<?php


namespace App\Exceptions;


class Msg
{
    const   Success           =   '成功';
    const   Unauthorized      =   '未認證';
    const   Failed            =   '失敗';
    const   CreateUserSuccess =   '建立使用者成功';
    const   CreateUserFailed  =   '建立使用者失敗';
    const   LoginSuccess      =   '登入成功';
    const   LoginFailed       =   '登入失敗';
    const   LoginOutSuccess   =   '退出成功';
    const   UserIsMe          =   '獲取當前使用者';

    const   CreatePostsSuccess  =   '建立文章成功';
    const   CreatePostsFailed   =   '建立文章失敗';
    const   PostsListSuccess    =   '查詢文章列表成功';
    const   PostsListFailed     =   '查詢文章列表失敗';
}
測試建立使用者

使用postman工具

建立使用者
建立使用者

登入使用者
登入使用者

當前使用者
當前使用者

使用者列表
使用者列表

退出登入
退出登入

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章