OAuth原理與Laravel Passport實作(2)
Laravel Passport 建置OAuth Service與Client
PS: Laravel版本為5.4,本篇略過.env與vhost等設定
首先準備兩個Laravel專案分別為oauth_service與oauth_client
- oauth_service當作Authorization Server與Resource Server
- oauth_client當作client
oauth_service 網址為http://service.oauth.test/
oauth_client 網址為http://client.oauth.test/
進到oauth_service專案根目錄
cd oauth_service
安裝Laravel OAuth套件
composer require laravel/passport
在config/app.php $providers中新增
Laravel\Passport\PassportServiceProvider::class
加入Laravel預設Auth
php artisan make:auth
執行migrate
php artisan migrate
如果出現以下Error,請在app/Providers/AuthServiceProvider.php中加入以下,再清空DB再執行
Schema::defaultStringLength(191);
[Illuminate\Database\QueryException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter tableusers
add uniqueusers_email_unique
([PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
執行passport
php artisan passport:install
會產生token加密金鑰
產生 Resource Owner Credentials Grant Flow 客戶端
產生 Client Credentials Grant Flow客戶端
修改User.php (app/User.php)
加入use Laravel\Passport\HasApiTokens; traits
在app/Providers/AuthServiceProvider.php
加入Passport::routes();
修改config/auth.php
將 guards 陣列中的 api driver 修改成 passport
Laravel 內建已配置用Vue.js寫好的前端OAuth註冊畫面
php artisan vendor:publish — tag=passport-components
打開resources/assets/js/app.js
Vue.component( ‘passport-clients’, require(‘./components/passport/Clients.vue’));Vue.component( ‘passport-authorized-clients’, require(‘./components/passport/AuthorizedClients.vue’));Vue.component( ‘passport-personal-access-tokens’, require(‘./components/passport/PersonalAccessTokens.vue’));
打開resources/assets/view/home.blade.php修改為以下
@extends('layouts.app')@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>
</div>
</div>
</div>
@endsection
安裝node 模組(需要安裝Node.js)
npm install
編譯assets
npm run dev
到http://service.oauth.test/register註冊帳號並登入
就會看到OAuth頁面
如果畫面是白色代表Browser把js cache住,打開resources/assets/view/layouts/app.blade.php對js/app.js加上版本號
點擊Create New Client 向OAuth註冊一個服務
Redirect URL 填回傳Authorization Grant網址
就會拿到Client ID與Secret
這邊叫Client ID但在Spec 2.2. Client Identifier中叫Client Identifier,兩者是一樣的意思,Spec中也未規定Client ID規則,只要是唯一字串就好
切到oauth_client專案
cd oauth_client
安裝guzzlehttp/guzzle
composer require guzzlehttp/guzzle
打開routes/web.php增加以下route
Route::get('/authorize', function () {
$query = http_build_query([
'client_id' => '3',
'redirect_uri' => 'http://client.oauth.test/token',
'response_type' => 'code',
'scope' => 'Email',
'state' => '1234',
]);
return redirect('http://service.oauth.test/oauth/authorize?' . $query);
});
Route::get('/token', function (Request $request) {
$http = new GuzzleHttp\Client;
$response = $http->post('http://service.oauth.test/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'redirect_uri' => 'http://client.oauth.test/token',
'code' => $request->code,
],
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Basic ' . base64_encode('3:yJfcjtOfmIoJfNuqAPiFXDzGHHvajCToSIwxWiTr'),
],
]);
return json_decode((string)$response->getBody(), true);
});
client_id與client_secret就是OAuth註冊完成所給的
/authorize : 代表Authorization Request
/token: 接收Authorization Grant並向Authorization Server取Access Token
切到oauth_service專案
cd oauth_service
到app/Providers/AuthServiceProvider.php加上
Passport::tokensCan([
'Profile' => 'Access your profile',
'Email' => 'Access your Email',
]);Passport::tokensExpireIn(Carbon::now()->addMinute(60));Passport::refreshTokensExpireIn(Carbon::now()->addDays(1));
profile 個資的存取權限
Email 電子郵件存取權限
限制Access Token expires為1小時
限制Refresh Token在一天內都可以刷新Access Token
PS: 默認Access Token expires無期限
到app/Http/Kernel.php加上
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
scope : Access Token有任意指定Scope的即可通過
scopes : Access Token指定Scope需都匹配才可通過
打開http://client.oauth.test/authorize,可以看到client對Resource Owner發出一個Authorization Request,內容為要求對Email的授權,此時Resource Owner決定是否授權,授權後就可以拿到Access Token等資料,這中間Client取得Authorization Grant再對Authorization Server請求Authorization Server這過程Resource Owner不可見的,由後端自行處理
打開routes/api.php修改成以下
Route::group(['prefix' => '/user', 'middleware' => 'auth:api'], function (){ Route::get('/profile', function (Request $request) {
return $request->user()->toArray();
})->middleware('scope:Profile');
Route::get('/email', function (Request $request) {
return $request->user()->email;
})->middleware('scope:Email');
});
http://service.oauth.test/api/user/email 取得user email但限制需有email權限
http://service.oauth.test/api/user/profile 取得user profile但限制需有profile權限
Access Token有email權限成功取email
Access Token沒有profile權限
以上就完成簡易的Laravel OAuth,後續會討論到上方提到的route參數
OAuth 2.0 Client 認證
在上一章節可以看到Client向OAuth註冊完成後會拿到Client ID與Secret,這兩組就是用來給Authorization Server認證Client所使用的
認證方式分為兩種,如下
- HTTP Basic Auth (推薦)
用 HTTP Basic Auth 來認證(參考 RFC 2617)
Client ID為3
Secret為yJfcjtOfmIoJfNuqAPiFXDzGHHvajCToSIwxWiTr
步驟:
- 將 Client ID與Secret 連起來,中間用冒號 : 分開
3:yJfcjtOfmIoJfNuqAPiFXDzGHHvajCToSIwxWiTr
- 用base64編碼(以php為例)
base64_encode('3:yJfcjtOfmIoJfNuqAPiFXDzGHHvajCToSIwxWiTr')
PS: base64只是一種編碼不是加密,所以請一定要走HTTPS
- 根據HTTP Basic Auth規定前綴要加上Basic並一個space
Basic Mzp5SmZjanRPZm1Jb0pmTnVxQVBpRlhEekdISHZhakNUb1NJd3hXaVRy
- Headers用Authorization
Authorization: Basic Mzp5SmZjanRPZm1Jb0pmTnVxQVBpRlhEekdISHZhakNUb1NJd3hXaVRy
Laravel版本
2. 使用POST發送(不推薦)
在無法使用 HTTP Basic Auth或其他 HTTP Authentication 方式的 Client 才使用
POST /token HTTP/1.1
Host: http://service.oauth.test/
Content-Type: application/x-www-form-urlencodedgrant_type=authorization_code&redirect_uri=http://client.oauth.test/token&client_id=3&client_secret=yJfcjtOfmIoJfNuqAPiFXDzGHHvajCToSIwxWiTr&code=1234
Laravel 版本
參考Spec 2.3.1. Client Password