Laravel

Laravel Airlock

视频教程:https://www.bilibili.com/video/av95269923
视频教程:https://www.bilibili.com/video/av96038774
视频教程:https://www.bilibili.com/video/av96088617

Laravel Airlock Sample

中文在下方。

Clone

git clone https://github.com/leonzai/laravel-airlock-sample.git

API Token Authentication

git checkout airlock-for-api-token
composer install
# Configure your database in the .env file.
php artisan migrate
php artisan serve

SPA Authentication

same domain
git checkout airlock-for-spa-same-domain
composer install
cp .env.example .env
# Configure your database in the .env file.
# Add "AIRLOCK_STATEFUL_DOMAINS=127.0.0.1:8000" to .env.
# You must change the domain to yours'.
php artisan migrate
php artisan key:generate
php artisan serve

To test: http://127.0.0.1:8000/AirlockTest.html. View the console in your browser.

sub domain

In this case, laravel develops APIs, and the front end is SPA, and they have the same second-level domain name.

git checkout airlock-for-spa-sub-domain
composer install
cp .env.example .env
# Configure your database in the .env file.
# Add "AIRLOCK_STATEFUL_DOMAINS=spa.test,m.spa.test" to .env.
# Add "SESSION_DOMAIN=.spa.test" to .env. 
# You must change the domains to yours'.
php artisan migrate
php artisan key:generate
# Configure two virtual hosts: spa.test, m.spa.test.They point to the same project which we just cloned. 

To test: http://spa.test/spa.html. View the console in your browser.

Detail

Initialize The Project With Airlock

# Install Airlock:
composer require laravel/airlock

# Generate configuration files and database migration files:
php artisan vendor:publish --provider="Laravel\Airlock\AirlockServiceProvider"

# Set length of default string type in Laravel database migration in AppServiceProvider:
\Schema::defaultStringLength(191);

# Configure your database in the .env file and migrate it.
php artisan migrate

Set all the response to json format.

create app\Http\Requests\BaseRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Http\Request;

class BaseRequest extends Request
{

    public function expectsJson()
    {
        return true;
    }

    public function wantsJson()
    {
        return true;
    }
}

# index.php
$response = $kernel->handle(
    $request = \App\Http\Requests\BaseRequest::capture()   # This line is replaced
);

Add methods to the User model to manipulate tokens.

We do not need this in SPA authentication if we never use any method with token.

use Laravel\Airlock\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable; 
}

API token

# routes/api.php

Route::post('auth/register', 'Auth\ApiController@register');
Route::post('auth/login', 'Auth\ApiController@login');
Route::post('auth/logout', 'Auth\ApiController@logout')->middleware('auth:airlock');
Route::post('auth/revoke/all/tokens', 'Auth\ApiController@revoke_all_tokens')->middleware('auth:airlock');
# app/Http/Controllers/Auth/ApiController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use \Validator;
use Illuminate\Http\Request;

class ApiController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:airlock')->except('login', 'register');
    }

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

    public function register(Request $request)
    {
        $data = $this->validator($request->all())->validate();

        $user = $this->create($data);

        return $this->issue_token($user);
    }

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users',],
            'name' => ['required', 'string', 'max:255', 'unique:users',],
            'password' => ['required', 'string', 'min:8', 'confirmed',],
        ]);
    }

    protected function create(array $data)
    {
        return User::forceCreate([
            'email' => $data['email'],
            'name' => $data['name'],
            'password' => password_hash($data['password'], PASSWORD_DEFAULT),
        ]);
    }

    public function logout()
    {
        auth()->user()->currentAccessToken()->delete();

        return ['message' => __('auth.sign_out_successfully')];
    }

    public function revoke_all_tokens()
    {
        if (!auth()->user()->tokenCan('*')) {
            abort(403, __('auth.forbidden'));
        }
        auth()->user()->tokens()->delete();

        return ['message' => __('auth.sign_out_successfully')];
    }

    public function login()
    {
        $user = User::where($this->username(), request($this->username()))
            ->firstOrFail();

        if (!password_verify(request('password'), $user->password)) {
            abort(403, __('auth.failed'));
        }

        return $this->issue_token($user);
    }

    /**
     * @param $user
     * @return array
     */
    protected function issue_token($user): array
    {
        return [
            'access_token' => $user->createToken('general user', ['general_user'])->plainTextToken
        ];
    }

}
Usage
return $user->createToken('token-name', ['server:update'])->plainTextToken;
if ($user->tokenCan('server:update')) {
    //
}

SPA Authentication

Laravel develops APIs, and SPA front end are under the same domain name.

use Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
# .env
AIRLOCK_STATEFUL_DOMAINS=your.domain
The current version of the spa authentication does not involve token scope functionality. If HasApiTokens is used in the User model, the logged-in user will have a super token scope. If there is no use HasApiTokens, an error will be reported when encountering a block of code that involves a token manipulation.
Laravel develops APIs, and SPA front end are under the same second-level domain name.
composer require fruitcake/laravel-cors
php artisan vendor:publish --tag="cors"
# config/cors.php

'paths' => ['path_your_want_to_cors', 'api/*'],
'supports_credentials' => true,
# app/Http/Kernel.php

protected $middleware = [
    // ...
    \Fruitcake\Cors\HandleCors::class,
];
# .env

AIRLOCK_STATEFUL_DOMAINS=spa.test,api.spa.test

SESSION_DOMAIN=.spa.test
use Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
// spa 页面 

axios.defaults.withCredentials = true;

To authenticate your SPA, your SPA’s login page should first make a request to the /airlock/csrf-cookie route to initialize CSRF protection for the application:

axios.get('/airlock/csrf-cookie');

Laravel Airlock 例子

克隆

git clone https://github.com/leonzai/laravel-airlock-sample.git

API Token 认证

git checkout airlock-for-api-token
composer install
# Configure your database in the .env file.
php artisan migrate
php artisan serve

SPA 认证

same domain
git checkout airlock-for-spa-same-domain
composer install
cp .env.example .env
# Configure your database in the .env file.
# Add "AIRLOCK_STATEFUL_DOMAINS=127.0.0.1:8000" to .env.
# You must change the domain to yours'.
php artisan migrate
php artisan key:generate
php artisan serve

测试地址: http://127.0.0.1:8000/AirlockTest.html。 测试时候请查看浏览器的控制台。

sub domain

在这种情况下,laravel开发API,前端是SPA,并且它们具有相同的二级域名。

git checkout airlock-for-spa-sub-domain
composer install
cp .env.example .env
# Configure your database in the .env file.
# Add "AIRLOCK_STATEFUL_DOMAINS=spa.test,m.spa.test" to .env.
# Add "SESSION_DOMAIN=.spa.test" to .env. 
# You must change the domains to yours'.
php artisan migrate
php artisan key:generate
# Configure two virtual hosts: spa.test, m.spa.test.They point to the same project which we just cloned. 

测试地址: http://spa.test/spa.html. 测试时候请查看浏览器的控制台。

细节

初始化项目和 Airlock

# 安装 Airlock:
composer require laravel/airlock

# 生成配置文件和数据库迁移文件:
php artisan vendor:publish --provider="Laravel\Airlock\AirlockServiceProvider"

# 在 AppServiceProvider 中设置 Laravel 数据库迁移中的默认字符串类型的长度:
\Schema::defaultStringLength(191);

# 配置数据库并进行数据库迁移
php artisan migrate

设置所有请求后的相应数据为 json 格式

新建 app\Http\Requests\BaseRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Http\Request;

class BaseRequest extends Request
{

    public function expectsJson()
    {
        return true;
    }

    public function wantsJson()
    {
        return true;
    }
}

# index.php
$response = $kernel->handle(
    $request = \App\Http\Requests\BaseRequest::capture()   # This line is replaced
);

给 User 模型添加用来操作 token 的方法

如果我们不使用任何带有令牌的方法,则在 SPA 身份验证中不需要下面的配置。

use Laravel\Airlock\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable; 
}

API token

# routes/api.php

Route::post('auth/register', 'Auth\ApiController@register');
Route::post('auth/login', 'Auth\ApiController@login');
Route::post('auth/logout', 'Auth\ApiController@logout')->middleware('auth:airlock');
Route::post('auth/revoke/all/tokens', 'Auth\ApiController@revoke_all_tokens')->middleware('auth:airlock');
# app/Http/Controllers/Auth/ApiController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use \Validator;
use Illuminate\Http\Request;

class ApiController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:airlock')->except('login', 'register');
    }

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

    public function register(Request $request)
    {
        $data = $this->validator($request->all())->validate();

        $user = $this->create($data);

        return $this->issue_token($user);
    }

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users',],
            'name' => ['required', 'string', 'max:255', 'unique:users',],
            'password' => ['required', 'string', 'min:8', 'confirmed',],
        ]);
    }

    protected function create(array $data)
    {
        return User::forceCreate([
            'email' => $data['email'],
            'name' => $data['name'],
            'password' => password_hash($data['password'], PASSWORD_DEFAULT),
        ]);
    }

    public function logout()
    {
        auth()->user()->currentAccessToken()->delete();

        return ['message' => __('auth.sign_out_successfully')];
    }

    public function revoke_all_tokens()
    {
        if (!auth()->user()->tokenCan('*')) {
            abort(403, __('auth.forbidden'));
        }
        auth()->user()->tokens()->delete();

        return ['message' => __('auth.sign_out_successfully')];
    }

    public function login()
    {
        $user = User::where($this->username(), request($this->username()))
            ->firstOrFail();

        if (!password_verify(request('password'), $user->password)) {
            abort(403, __('auth.failed'));
        }

        return $this->issue_token($user);
    }

    /**
     * @param $user
     * @return array
     */
    protected function issue_token($user): array
    {
        return [
            'access_token' => $user->createToken('general user', ['general_user'])->plainTextToken
        ];
    }

}
用法
return $user->createToken('token-name', ['server:update'])->plainTextToken;
if ($user->tokenCan('server:update')) {
    //
}

SPA 认证

Laravel API,SPA 前端使用相同的域名。
use Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
# .env
AIRLOCK_STATEFUL_DOMAINS=your.domain
SPA 认证方式目前的版本不涉及 token 范围功能。如果在 User 模型中 use HasApiTokens,那么登录的用户就会拥有超级 token 范围。如果没有 use HasApiTokens,在碰到涉及到 token 范围的代码块就会报错。
Laravel API,SPA 前端使用相同的二级域名。
composer require fruitcake/laravel-cors
php artisan vendor:publish --tag="cors"
# config/cors.php

'paths' => ['path_your_want_to_cors', 'api/*'],
'supports_credentials' => true,
# app/Http/Kernel.php

protected $middleware = [
    // ...
    \Fruitcake\Cors\HandleCors::class,
];
# .env

AIRLOCK_STATEFUL_DOMAINS=spa.test,api.spa.test

SESSION_DOMAIN=.spa.test
use Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
// spa 页面 

axios.defaults.withCredentials = true;

要验证您的 SPA,您的 SPA 的登录页面应首先向 /airlock/csrf-cookie 路由发出请求,以初始化该应用程序的CSRF保护:

axios.get('/airlock/csrf-cookie');

发表评论