Laravel Passport 基于
thephpleague/oauth2-server
包实现了完整的OAuth2
服务
安装配置
composer require laravel/passport
- 注册
Laravel\Passport\PassportServiceProvider::class
- 迁移
passport
的表
php artisan vendor:publish --tag=passport-migrations
php artisan migrate
- 生成
Token
秘钥php artisan passport:keys
User
模型增加use Laravel\Passport\HasApiTokens
api guard
的driver
切换为passport
- 注册
OAuth2
路由
# AuthServiceProvider::boot中注册令牌签发撤销相关的路由
- /oauth/authorize
- /oauth/token
Passport::routes()
- 设定
Token
过期时间(默认不过期)
# AuthServiceProvider::boot中设置过期时间
Passport::tokensExpireIn(Carbon::now()->addDays(15))
Passport::refreshTokensExpireIn(Carbon::now()->addDays(30))
前端骨架
Passport
提供了一套方便的JsonAPI
式的Vue
组件用于OAuth2
相关服务
# 发布vue组件至resources/assets/js/components
php artisan vendor:publish --tag=passport-components
# resources/assets/js/app.js中注册组件
- passport-clients -> Clients.vue
- passport-authorized-clients -> AuthorizedClients.vue
- passport-personal-access-tokens -> PersonalAccessTokens.vue
# 编译asset前端程序
npm run dev
# 使用组件服务
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>
Client管理
1. Artisan方式管理
# 授权服务器上注册客户端应用
php artisan passport:client
- 提供:应用名、授权后回调地址等
- 响应:client ID、secret
2. JsonAPI方式管理
# 认证用户名下的客户端列表
GET /oauth/clients
# 为认证用户新建客户端
POST /oauth/clients
- 数据:{name:'', redirect:''}
- 响应:client ID、client secret
# 为认证用户更新客户端
PUT /oauth/clients/{client-id}
- 请求:{name:'', redirect:''}
- 响应:client ID、client secret
# 删除认证用户名下的客户端
DELETE /oauth/clients/{client-id}
授权模式
OAuth2 设计了四种授权模式用于不同的应用场景
1. 授权码 - 授权模式
- 通过授权码的跳转工作流来签发
Token
- 广泛用于用户对第三方应用的引导授权
# 客户端应用引导用户至授权服务器
客户端:Get /redirect
授权服务器:Get /oauth/authorize?client_id=&redirect_uri=http://example.com/callback&response_type=code&scope=
# 用户同意授权后回跳至redirect_uri
- redirect_uri必须和client注册时的redirect一致
- 定制授权页面
- php artisan vendor:publish --tag=passport-views
- 视图路径 resources/views/vendor/passport
# 授权码交换token
- 客户端:Get /callback(即redirect_uri)
- 授权服务器:Post /oauth/token
- 数据:{grant_type:'authorization_code', client_id:'', client_secret:'', redirect_uri:'http://example.com/callback', code:''}
- 响应:access_token、refresh_token、expires_in
# 刷新Token
授权服务器: Post /oauth/token
- 数据:{grant_type:'refresh_token', refresh_token:'', client_id:'', client_secret:'', scope:''}
- 响应:access_token、refresh_token、expires_in
2. 密码 - 授权模式
- 不用像第三方客户端应用那样通过授权码跳转
- 方便于用户(数据属主)直接通过账号密码来交换
Token
创建密码授权客户端
php artisan passport:client --password
请求Token
授权服务器:Post /oauth/token
- 数据:{grant_type:'password', client_id:'', client_secret:'', username:'', password: '', scope: ''} #密码授权模式可以指定scope为*全部权限域
- 响应:access_token、refresh_token、expires_in
3. 简化 - 授权模式
- 类似于授权码授权模式,但是简化掉了授权码交换的操作
- 常用于JS应用、手机应用等客户端凭据不能安全存储的场景
# AuthServiceProvider::boot中启用简化授权
Passport::enableImplicitGrant()
客户端:Get /redirect
授权服务器:/oauth/authorize?client_id=&redirect_uri=http://example.com/callback&response_type=token&scope=
4. 客户端凭据 - 授权模式
- 常用于直接面向内部应用的授权(没有用户参与)
授权服务器:Post /oauth/token
- 数据:{grant_type:'client_credentials', client_id:'', client_secret:'', scope: ''} #密码授权模式可以指定scope为*全部权限域
- 响应:access_token、refresh_token、expires_in
私人访问Token
- 该场景不属于
OAuth2
授权模式 - 用户自我签发私人访问
Token
便于API
测试 - 该
Token
总是有效的, 不随过期配置而变化
# 创建私人访问客户端
php artisan passport:client --personal
# 管理私人访问Token
$scopes = []
$token = $user->createToken('token_name' [, $scopes])->accessToken
## JsonAPI管理 ##
GET /oauth/scopes #所有权限域列表
GET /oauth/personal-access-tokens #用户创建的所有私人访问Token
POST /oauth/personal-access-tokens {name:'token_name', scopes:[]} #新建私人访问令牌
DELETE /oauth/personal-access-tokens/{token-id} #删除私人访问令牌
模拟用户+Scope
# 主要用于测试,当前用户模拟为指定用户
Passport::actingAs($user, array scopes)
Artisan快捷方式
# 快捷生成三个组件:
- token秘钥
- 密码授权客户端
- personal access client
php artisan passport:install
路由保护
->middleware('auth:api')
(配置api guard
使用passport driver
)- 客户端请求需带上头部
Accept: application/json
Authorization: Bearer Token
Token Scopes
定义Scope
- 代表了一个权限组
# AuthServiceProvider::boot中定义
Passport::tokensCan([
'权限名' => '权限描述',
...
]);
Token设定Scope
- 授权码模式
客户端:Get /redirect
授权服务器:Get /oauth/authorize?client_id=&redirect_uri=http://example.com/callback&response_type=code&scope=权限列表(空格隔开)
- 私人令牌模式
$scopes = []
$token = $user->createToken('token_name' [, $scopes])->accessToken
检查Scope
# `\App\Http\Kernel::$routeMiddleware`注册以下路由中间件
- 'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class
* 检查满足所有权限域
* `->middleware('scopes:权限域1,...')`
- 'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class
* 检查满足任一权限域
* `->middleware('scope:权限域1,...')`
# 直接检查用户Token是否拥有指定权限域
$user->tokenCan('权限域')
JS访问API
web
中间件增加\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class
- 自动在响应上设置一个
laravel_token cookie
(内含一个加密的JWT
) - 不用再显式配置
HTPP
头部的Token
- 自动在响应上设置一个
- 根据
JS框架
实际情况配置请求自动带上头部X-CSRF-TOKEN
:CSRF Token
X-Requested-With
:XMLHttpRequest
Token签发事件
Laravel\Passport\Events\AccessTokenCreated
Laravel\Passport\Events\RefreshTokenCreated
注意点
- 凭据Credential和令牌Token的区别理解(分别是独立存储,Credential处理client_id/secret)