Laravel Passport无状态API认证:弃用Password Grant后的授权码模式适配问题
无状态API下Laravel Passport替代Password Grant的解决方案
针对你的核心问题,这里提供三种可行方案,适配无状态API的需求:
方案1:继续使用Password Grant(虽被弃用但仍可用)
官方标记Password Grant为弃用只是出于安全最佳实践的建议(避免直接传输用户密码),但该功能并未从Passport中移除。如果你的API服务仅对接信任的客户端(比如自家的移动端应用、内部服务),这种方式是最直接高效的,完全适配无状态场景:
- 创建密码授权客户端:
php artisan passport:client --password
执行后会生成client_id和client_secret,把它们存入环境变量或配置文件。
- 编写登录API端点:
use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; Route::post('/api/login', function (Request $request) { $request->validate([ 'email' => 'required|email', 'password' => 'required', ]); $response = Http::asForm()->post(config('app.url').'/oauth/token', [ 'grant_type' => 'password', 'client_id' => config('passport.password_client.id'), 'client_secret' => config('passport.password_client.secret'), 'username' => $request->email, 'password' => $request->password, 'scope' => '', ]); return response()->json($response->json(), $response->status()); });
这个端点直接调用Passport的token接口,返回包含access_token和refresh_token的响应,无需重定向,完全基于api守卫工作。
注意:务必确保API全程使用HTTPS,避免密码明文传输风险。
方案2:使用Authorization Code Grant with PKCE
如果你的客户端是非信任的外部应用(比如第三方SPA、移动APP),推荐使用带PKCE的授权码模式,它可以替代客户端密钥,同时规避重定向对无状态服务的限制:
- 创建公开授权客户端:
php artisan passport:client --public
此客户端无需client_secret,适合前端类客户端。
- 客户端侧流程调整:
- 客户端生成随机的
code_verifier和对应的code_challenge - 客户端直接向Passport的授权端点请求授权码(无需服务端重定向,由客户端自行处理授权页面的跳转与回调)
- 拿到授权码后,客户端直接调用
/oauth/token交换access_token和refresh_token
- 配置API守卫:
在config/auth.php中确保api守卫使用Passport驱动:
'guards' => [ 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
这种模式完全符合OAuth 2.0官方推荐,同时适配无状态API的架构。
方案3:自定义Grant类型(完全适配无状态需求)
如果不想依赖弃用的Password Grant,也不想处理授权码的跳转逻辑,可以自定义一个支持刷新令牌的Grant类型,完全基于api守卫工作:
- 创建自定义Grant类:
<?php namespace App\Passport; use League\OAuth2\Server\Grant\AbstractGrant; use League\OAuth2\Server\RequestEvent; use Psr\Http\Message\ServerRequestInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\Exception\OAuthServerException; class PasswordRefreshGrant extends AbstractGrant { public function getName() { return 'password_refresh'; } public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, \DateInterval $accessTokenTTL ) { // 验证客户端合法性 $client = $this->validateClient($request); // 验证用户账号密码 $user = $this->validateUser($request); // 生成访问令牌 $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getAuthIdentifier(), []); // 生成刷新令牌 $refreshToken = $this->issueRefreshToken($accessToken); $responseType->setAccessToken($accessToken); $responseType->setRefreshToken($refreshToken); return $responseType; } protected function validateUser(ServerRequestInterface $request) { $email = $this->getRequestParameter('email', $request); $password = $this->getRequestParameter('password', $request); if (is_null($email) || is_null($password)) { throw OAuthServerException::invalidRequest('email or password'); } $user = $this->getUserEntityByUserCredentials( $email, $password, $this->getName(), $this->getClientEntityOrFail($request) ); if ($user === null) { throw OAuthServerException::invalidCredentials(); } return $user; } }
- 注册自定义Grant:
在AuthServiceProvider的boot方法中添加:
use App\Passport\PasswordRefreshGrant; use Laravel\Passport\Passport; public function boot() { $this->registerPolicies(); Passport::routes(); // 启用自定义Grant类型 app(\League\OAuth2\Server\AuthorizationServer::class)->enableGrantType( new PasswordRefreshGrant(), Passport::tokensExpireIn() ); }
- 使用自定义Grant获取令牌:
客户端直接POST请求/oauth/token,参数如下:
grant_type: password_refresh client_id: 你的客户端ID email: 用户邮箱 password: 用户密码 scope: (可选)权限范围
此请求会直接返回access_token和refresh_token,全程无重定向,完全适配无状态API。
内容的提问来源于stack exchange,提问作者Eugene Zdravstvuy
相关产品推荐
相关产品推荐

