跳至主要內容

Laravel授权

OrangBus大约 6 分钟

Passport 多表认证

自定义验证

// UserModel

/**
     * 自定义username验证字段
     */
    public function findForPassport($username)
    {
        return $this->where("phone", $username)->first();
    }

    /**
     * 通过Passport的密码授权验证用户使用的密码
     *
     * @param string $password
     * @return bool
     */
    public function validateForPassportPasswordGrant($password)
    {
        return $password == $this->password ? true : false;
//        return Hash::check($password, $this->password);
    }

    public function getUserByPhone($phone)
    {
        return $this->where("phone", $phone)->first();
    }

1、创建授权令牌

2、路由设置: app/Providers/RouteServiceProvider.php

Route::prefix('api')
->middleware([
    'web',
    "client:admin,admin", // scopes授权名称,作用域
    "auth:admin",  // 认证的grud
    "checkUserAuth",
    "scope:admin"
])
->namespace($this->namespace)
->group(base_path('routes/api.php'));

授权方法

<?php
/**
 * Created by OrangBus
 * User email: orangbus40400@gmail.com
 * website: orangbus.cn
 * blog: doc.orangbus.cn
 * github: github.com/orangbus
 */

namespace App\Traits;

use App\Enum\ArgumentEnum;
use App\Exceptions\ArgumentEmptyException;
use App\Exceptions\BusinessException;
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

trait PassportSecret
{

    /**
     * 获取client_id
     * @param string $provider
     * @return mixed
     */
    private function getClientId($provider = 'user')
    {
        $list = [
            "user" => config("app.CLIENT_ID"),
            "admin" => config("app.ADMIN_CLIENT_ID"),
            "platform" => config("app.PLATFORM_CLIENT_ID"),
            "partner" => config("app.PARTNER_CLIENT_ID"),
        ];
        return $list[$provider];
    }

    public function getClientSecret($provider = 'user')
    {
        $list = [
            "user" => config("app.CLIENT_SECRET"),
            "admin" => config("app.ADMIN_CLIENT_SECRET"),
            "platform" => config("app.PLATFORM_CLIENT_SECRET"),
            "partner" => config("app.PARTNER_CLIENT_SECRET"),
        ];
        return $list[$provider];
    }

    /**
     * 获取token
     * @param $user
     * @param string $provider admin|platform
     * @param string $scope
     * @return mixed
     */
    private function getToken($user, $provider = "user", $scope = "*")
    {
        $response = Http::withOptions(["verify" => false])->asForm()->post($this->getTokenUrl(), [
            'grant_type' => 'password',
            'username' => $user->id,
            'password' => $user->password,
            'client_id' => $this->getClientId($provider),
            'client_secret' => $this->getClientSecret($provider),
            'scope' => $scope,
            'provider' => $provider, //设置provider
        ]);
        $status = $response->status();
        $body = json_decode($response->body(), true);
        if ($status !== 200) {
            // error,error_description,message
            throw new BusinessException($body['error'] ?? "授权服务器错误", $status);
        }
        return $body;
    }

    /**
     * @param $refreshToken
     * @param string $scope
     * @return mixed
     */
    public function refresh($refreshToken, $provider = "*", $scope = "*")
    {
        $response = Http::withOptions(["verify" => false])->asForm()->post($this->getTokenUrl(), [
            'grant_type' => 'refresh_token',
            'refresh_token' => $refreshToken,
            'client_id' => $this->getClientId($provider),
            'client_secret' => $this->getClientSecret($provider),
            'scope' => $scope,
            'provider' => $provider
        ]);
        $status = $response->status();
        $body = json_decode($response->body(), true);
        if ($status !== 200) {
            throw new BusinessException($body['error_description'] ?? '服务器错误', $status);
        }
        return $body;
    }

    /**
     * 获取认证用户的所有客户端
     */
    public function getAuthClients()
    {
        $response = Http::withOptions(["verify" => false])->asForm()->get("/oauth/clients");
        $status = $response->status();
        if ($status !== 200) {
            throw new BusinessException("请求错误", $status);
        }
        return json_decode($response->body(), true);
    }

    /**
     * 获取 token 请求地址
     */
    private function getTokenUrl(): string
    {
        return config("app.url") . "/oauth/token";
    }

    /**
     * 获取刷新 token 请求地址
     */
    private function getRefreshTokenUrl()
    {
        return config("app.url") . "/oauth/refresh_token";
    }
}

获取token

$token = $this->getToken($user, "platform", 'platform'); 

撤销授权

$this->request->user()->token()->revoke();

配置中间件

 'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            // passport,必须放在最后
            \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
        ],

protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,

        // passport 主要配置
        'client' => CheckClientCredentials::class,
        "passportProvider" => PassportProvider::class,
        // 作用于检查
        'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
        'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
    
        // 检查用户信息
        "checkUserAuth" => CheckUserAuth::class,
        // 平台登录鉴权
        "PlatFormLogin" => PlatFormLogin::class,
        // 商户登录鉴权路由
        "MerchantLogin" => MerchantLogin::class,

        // 商户路由检查
        "checkRouteAuth" => CheckRouteAuth::class,
    ];

切换授权中间件app/Http/Middleware/CheckUserAuth.php

<?php

namespace App\Http\Middleware;

use App\Enum\GlobalEnum;
use App\Events\LogUserErrorInfo;
use App\Exceptions\BusinessException;
use App\Exceptions\DangerErrorExcetion;
use Closure;
use Illuminate\Http\Request;

class CheckUserAuth
{
    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $authorization = $request->header("authorization");
        if (empty($authorization)) {
            return abort(GlobalEnum::AuthorizationErrorCode, GlobalEnum::AuthorizationErrorMessage);
        }
        // 用户身份验证设置
        $user = $request->user();
        $provider = $user->provider;
        if (is_null($provider) || empty($provider) || $user->mer_id <= 0) {
            // 记录错误用户信息
            event(new LogUserErrorInfo("用户类型错误或者mer_id为0异常", $request->user(), GlobalEnum::UserInfoError_TYPE));
            throw new DangerErrorExcetion(GlobalEnum::UserInfoErrorMessage, GlobalEnum::UserInfoErrorCode);
        }
        // 主要是这个
        \Config::set('auth.guards.api.provider', $provider);
        return $next($request);
    }
}

撤销登录

  public static function makeMerchantLogout($mer_id)
    {
        if (empty($mer_id)) return true;
        // 获取商户用户
        $adminIds = Admin::where("mer_id", $mer_id)->pluck("id")->toArray();
        return DB::table("oauth_access_tokens")
            ->where("client_id", env("ADMIN_CLIENT_ID"))
            ->whereIn("user_id", $adminIds)
            ->delete();
    }

授权:app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use App\Enum\GlobalEnum;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
use Laravel\Passport\RouteRegistrar;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
//         'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies(); // 授权域
        //passport 默认只会认证 config('auth.guards.api.provider') 指定的模型 想要多表认证 需要动态改变认证模型
        Passport::routes(function (RouteRegistrar $router) {
            // 可以选择注册哪些passport路由,此处只注册管理 Token 的路由
            $router->forAccessTokens();
        }, ['middleware' => ['checkUserAuth']]); //'passportProvider',

         注册 passport 路由
        Passport::routes();
        // passport 秘钥加密
        Passport::hashClientSecrets();

        // token 15天时效
        Passport::tokensExpireIn(now()->addDays(GlobalEnum::TokenExpireTime));
        // 刷新token时效时间 30天
        Passport::refreshTokensExpireIn(now()->addDays(GlobalEnum::RefreshTokenExpireTime));
        // 个人访问 token 时效时间6天
        Passport::personalAccessTokensExpireIn(now()->addMonths(GlobalEnum::PersonalAccessTokenExpireTime));
        // 令牌的作用于,防止出现窜表
        Passport::tokensCan([
            "user" => "用户表",
            "admin" => "商户用户表",
            "platform" => "平台用户表",
        ]);
    }
}

配置文件

<?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' => 'web',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | 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"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],
        'admin' => [
            'driver' => 'passport',
            'provider' => 'admin',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | 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\Models\User::class,
        ],
        'admin' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | 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 each reset token will 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' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 10800,

];

sanctum多角色(多表)认证

在项目中存在商户和客户两种身份,使用 sanctum 做的 api 认证,

但是问题是 Merchant model 生成的 token 不能通过 auth:merchant 中间件的验证,反而是 Customer model 生成的 token 可以通过 auth:merchant 中间件的验证。请问这块是否有对应的配置能让 token 和对应的验证项对应起来。

配置和代码如下:config/auth.php

'guards' => [
    'customer' => [
    'driver' => 'sanctum',
    'provider' => 'customers'
    ],

    'merchant' => [
    'driver' => 'sanctum',
    'provider' => 'merchants'
    ]
],

'providers' => [
    'customers' => [
    'driver' => 'eloquent',
    'model' => App\Models\Customer::class,
    ],

    'merchants' => [
    'driver' => 'eloquent',
    'model' => App\Models\Merchant::class
    ]
],

Model

use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Merchant extends Authenticatable
{
    use HasFactory, SoftDeletes, HasApiTokens;
}
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Customer extends Authenticatable
{
    use HasFactory, SoftDeletes, HasApiTokens;
}

merchant.route.php

Route::post('login', [LoginController::class, 'login']);
Route::middleware(['auth:merchant'])->group(function (){
    Route::get('test', [ProductController::class, 'test']);
});

customer.route.php

Route::post('login', [LoginController::class, 'login']);
Route::middleware(['auth:customer'])->group(function (){
     Route::get('test1', [ProductController::class, 'test1']);
});

MerchantController

public function login(Request $request)
{
    $merchant = Merchant::query()->find(1);
    $token = $merchant->createToken($merchant->name);
    return $this->ok([
    'token' => $token->plainTextToken
    ]);
}

CustomerController

public function login(Request $request)
{
    $customer = Customer::query()->find(1);
    $token = $customer->createToken($merchant->name);
    return $this->ok([
    'token' => $token->plainTextToken
    ]);
}

动作授权: AuthServiceProvider.php

$this->registerPolicies();

// 接口授权
Gate::define("user",function ($user){
    return $user->provider === "user";
});
// 后台授权
Gate::define("admin",function ($user){
    return $user->provider === "admin";
});