jundayw/laravel-oauth

Laravel OAuth provides a featherweight authentication system for SPAs and simple APIs.

v3.0.0 2023-06-19 04:00 UTC

This package is auto-updated.

Last update: 2024-11-19 07:05:07 UTC


README

为 SPA(单页应用程序)、移动应用程序和基于令牌的、简单的 API 提供轻量级身份验证系统。

允许应用程序的每个用户为他们的帐户生成多个 API 令牌。 这些令牌可以被授予指定允许令牌执行哪些操作的权限。

工作原理

为了解决两个独立问题而生

API 令牌

首先,它是一个简单的包,用于向用户发出 API 令牌,而不涉及 OAuth2

这个特性是通过将用户 API 令牌存储在单个数据库表中,并通过包含了有效 API 令牌的 Authorization 标识头对传入的请求进行身份验证而实现的。

SPA 身份验证

其次,提供了一种简单的方法来认证需要与基于 Laravel 的 API 进行通信的单页应用程序 (SPAs)。

这些 SPAs 可能与 Laravel 应用程序存在于同一仓库中,也可能是一个完全独立的仓库,例如使用 Vue CLI 或者 Next.js 创建的单页应用。

安装方法

您可以通过 Composer 软件包管理器安装:

composer require jundayw/laravel-oauth

接下来,你需要使用 vendor:publish Artisan 命令发布的配置和迁移文件。

配置文件将会保存在 config 文件夹中:

php artisan vendor:publish --provider="Jundayw\LaravelOAuth\OAuthServiceProvider"

或单独发布配置文件

php artisan vendor:publish --tag=oauth-config

或单独发布迁移文件

php artisan vendor:publish --tag=oauth-migrations

最后,您应该运行数据库迁移。

php artisan migrate --path=database/migrations/2022_07_23_160710_create_oauth_table.php

自定义迁移

如果你不想使用默认迁移,你应该在 App\Providers\AppServiceProvider 类的 register 方法中调用 OAuth::ignoreMigrations() 方法。

您可以通过执行以下命令导出默认迁移:

php artisan vendor:publish --tag=oauth-migrations

重写 OAuthToken 模型

通常,您应该在应用程序的服务提供器的 boot 方法中调用此方法:

use App\Models\OAuthToken;
use Jundayw\LaravelOAuth\OAuth;

/**
 * 引导应用程序服务。
 *
 * @return void
 */
public function boot()
{
    OAuth::oAuthTokenModelUsing(OAuthToken::class);
}

重写 RefreshToken 模型

通常,您应该在应用程序的服务提供器的 boot 方法中调用此方法:

use App\Models\RefreshToken;
use Jundayw\LaravelOAuth\OAuth;

/**
 * 引导应用程序服务。
 *
 * @return void
 */
public function boot()
{
    OAuth::refreshTokenModelUsing(RefreshToken::class);
}

配置

配置文件 config/oauth.php

return [
    // 加密秘钥
    'secret' => env('OAUTH_SECRET', env('APP_KEY')),

    // 加密方法
    'hash' => 'sha256',
    
    // 数据库存储令牌表
    'table' => 'oauth',
    
    // 访问令牌过期时间
    'access_token_expire_in' => 2 * 3600,
    
    // 刷新令牌过期时间
    'refresh_token_expire_in' => 24 * 3600 * 15,
    
    // 多[客户端/设备]是否同时在线,默认:开启
    // 如:同一账户是否允许[手机/电脑]同时在线
    'multiple_devices' => true,
    
    // 相同[客户端/设备]是否同时在线,默认:开启
    // 如:同一账户是否允许电脑端同时在线
    'concurrent_device' => true,
];

授权看守器

配置文件 config/auth.php 中, 将授权看守器 guardsdriver 参数的值设置为 oauth

return [
    // ...
    'guards' => [
        // 如果只有一个 oauth 看守器 provider 可为 null
        'client' => [
            'driver' => 'oauth',
            'provider' => null,
        ],
        // 如果多个 oauth 看守器 provider 需要配置
        'manager' => [
            'driver' => 'oauth',
            'provider' => 'managers',
        ],
    
        'user' => [
            'driver' => 'oauth',
            'provider' => 'users',
        ],
    ],

    'providers' => [
        'managers' => [
            'driver' => 'eloquent',
            'model' => App\Models\Manager::class,
        ],
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
    ],
    // ...
];

解决冲突

laravel 框架默认内置了(Sanctum 在处理 APP 开发时,无法实现令牌刷新功能)此时使用 OAuth 将造成冲突,解决方案:

使用 HasAccessTokens 功能替代 HasApiTokens,如果你其他模块使用到了 Sanctum

use Jundayw\LaravelOAuth\Contracts\HasAccessTokensContract;
use Jundayw\LaravelOAuth\HasAccessTokens;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable implements HasAccessTokensContract
{
    use HasApiTokens, HasAccessTokens {
        HasAccessTokens::currentAccessToken insteadof HasApiTokens;
        HasAccessTokens::withAccessToken insteadof HasApiTokens;
        HasAccessTokens::createToken insteadof HasApiTokens;
        HasAccessTokens::tokens insteadof HasApiTokens;
        HasAccessTokens::tokenCan insteadof HasApiTokens;
    }
}

也可以将 HasApiTokens 删除

use Jundayw\LaravelOAuth\Contracts\HasAccessTokensContract;
use Jundayw\LaravelOAuth\HasAccessTokens;

class User extends Authenticatable implements HasAccessTokensContract
{
    use HasAccessTokens;
}

发布 API Tokens

允许你发布 API 令牌,用于对你的应用程序的 API 请求进行身份验证。 使用 API 令牌发出请求时,令牌应作为 Bearer 令牌包含在 Authorization 请求头中。

use Jundayw\LaravelOAuth\Contracts\HasAccessTokensContract;
use Jundayw\LaravelOAuth\HasAccessTokens;

class User extends Authenticatable implements HasAccessTokensContract
{
    use HasAccessTokens;
}

要发布令牌,你可以使用 createToken 方法。 createToken 方法返回一个 Jundayw\LaravelOAuth\Token 实例。

use Illuminate\Http\Request;

Route::post('/tokens/create', function(Request $request) {
    return $request->user()
        ->createToken($request->token_name, $request->device_name, ['check-status', 'place-orders'])
        ->toArray();
});

你可以使用 HasAccessTokens trait 提供的 tokens Eloquent 关系访问用户的所有令牌:

foreach($user->tokens as $token) {
    //
}

刷新 API Tokens

use Illuminate\Http\Request;
use Jundayw\LaravelOAuth\RefreshToken;

Route::post('/tokens/refresh', function(Request $request, RefreshToken $refreshToken) {
    return $refreshToken->refreshToken($refreshToken->findTokenByRequest($request))->toArray();
});

令牌作用域

OAuth 内置 scopesscope 中间件:

scopes 中间件可以分配给一个路由,以验证传入请求的令牌是否具有所有列出的能力:

Route::get('/orders', function() {
    // Token has both "check-status" and "place-orders" abilities...
})->middleware(['auth:client', 'scopes:check-status,place-orders']);

scope 中间件可以分配给一个路由,以验证传入请求的令牌是否具有至少一个列出的能力:

Route::get('/orders', function() {
    // Token has the "check-status" or "place-orders" ability...
})->middleware(['auth:client', 'scope:check-status,place-orders']);

保护路由

use Illuminate\Http\Request;

Route::middleware(['api', 'auth:client'])->get('/user', function(Request $request) {
    return $request->user();
});

最佳实践

第一步:配置文件 config/auth.php

return [
    // ...
    'guards' => [
        'manager' => [
            'driver' => 'oauth',
            'provider' => 'managers',
        ],
    
        'user' => [
            'driver' => 'oauth',
            'provider' => 'users',
        ],
    ],

    'providers' => [
        'managers' => [
            'driver' => 'eloquent',
            'model' => App\Models\Manager::class,
        ],
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
    ],
    // ...
];

第二步:实现接口

use Jundayw\LaravelOAuth\Contracts\HasAccessTokensContract;
use Jundayw\LaravelOAuth\HasAccessTokens;

class User extends Authenticatable implements HasAccessTokensContract
{
    use HasAccessTokens;
}
use Jundayw\LaravelOAuth\Contracts\HasAccessTokensContract;
use Jundayw\LaravelOAuth\HasAccessTokens;

class Manager extends Authenticatable implements HasAccessTokensContract
{
    use HasAccessTokens;
}

第三步:配置文件 routes/web.php

use App\Models\User;
use App\Models\Manager;
use Illuminate\Http\Request;
use Jundayw\LaravelOAuth\RefreshToken;

// 发布 user 令牌
Route::get('/user', function(User $user) {
    return $user->first()?->createToken('测试-user', 'APP')->toArray();
});

// 发布 manager 令牌,有作用域
Route::get('/manager', function(Manager $manager) {
    return $manager->first()
        ?->createToken('测试-manager', 'PC', ['snsapi_base', 'snsapi_userinfo'])
        ->toArray();
});

// 获取 user 当前账户
Route::middleware(['auth:user'])->post('/user-info', function(Request $request) {
    return $request->user();
});

// 获取 manager 当前账户,验证作用域
Route::middleware(['auth:manager', ['scope:snsapi_userinfo']])
    ->post('/manager-info', function(Request $request) {
        return $request->user();
    });

// 刷新当前账户
Route::get('/refresh', function(\Illuminate\Http\Request $request, \Jundayw\LaravelOAuth\RefreshToken $refreshToken) {
    return $refreshToken->refreshToken($refreshToken->findTokenByRefreshToken($request->bearerToken()))->toArray();
});