轉自:http://www.dahouduan.com/2017/11/21/oauth2-php/php
https://blog.csdn.net/halsonhe/article/details/81030319html
Oauth2.0 是一個開源的受權協議,在全世界獲得普遍應用,比較大的社交服務都支持了Oauth2.0 協議,例如 QQ,微博,微信。nginx
假設有一個叫「教程集」的網站,能夠經過讀取用戶在微信裏的好友關係查詢到還有誰也在學習教程,用戶想使用該服務,就必須讓「教程集」讀取本身在微信裏的好友關係。git
微信只有獲得用戶的受權纔會容許「教程集」 讀取用戶的好友關係,這時候傳統的方式是,用戶將微信賬號密碼提交給「教程集」,「教程集」使用用戶的賬號密碼登陸微信,再獲取到用戶的好友關係。github
可是這樣的作法有幾個缺點:sql
Oauth2 正是用來解決以上場景遇到的問題的。數據庫
爲了描述方便仍是用微信舉例子:json
其中最關鍵的是第 「2」,即客戶端如何獲取用戶的受權,客戶端拿到受權碼就能夠向認證服務器換取令牌,Oauth2 提供了4種受權的方式:瀏覽器
下面咱們主要講下經常使用的受權碼模式。緩存
受權碼模式,即咱們最經常使用的受權模式,目前微博、微信、QQ 等都是用的這種受權方式,這種受權模式是最嚴密,功能最完整的。
主要流程以下:
access_token
向資源服務器獲取資源。步驟1中涉及的參數:
參數名 | 必填 | 說明 |
---|---|---|
response_type | 是 | 此處必須爲「code」 |
client_id | 是 | 客戶端id |
redirect_uri | 否 | 重定向 URI |
scope | 否 | 申請的受權範圍 |
state | 否 | 客戶端當前狀態,能夠是任意值,認證服務器會原樣返回這個參數 |
例子:
GET http://oauth2-server.dev/authorize.php?response_type=code&client_id=testclient&state=xyz&redirect_uri=http%3A%2F%2F127.0.0.1%3A8001%2F%2Fclient.php
步驟3 中,認證服務器迴應客戶端的URI,包含如下參數:
參數名 | 必填 | 說明 |
---|---|---|
code | 是 | 受權碼, 該受權碼有效期很短,例如30秒,且只能使用一次。 |
state | 否 | 若是客戶端中請求中包含這個參數,認證服務器的響應也必須包含一樣的參數和值 |
例子
HTTP/1.1 302 Found Location: http://oauth2-client.dev/client.php?code=ef2d9cd1bc71d99fa4ad193beab1bff48ec65df4&state=xyz
步驟4 中,客戶端向認證服務器申請令牌,包含如下參數:
參數名 | 必填 | 說明 |
---|---|---|
grant_type | 是 | 表示受權模式,此處值固定爲 」authorized_code」 |
code | 是 | 上一步得到的受權碼 |
redirect_uri | 是 | 重定向URI,必須跟步驟1中的該參數值保持一致。 |
client_id | 是 | 表示客戶端id |
例子:
POST /token.php HTTP/1.1 Host: 127.0.0.1:8001 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&&redirect_uri=http%3A%2F%2F127.0.0.1%3A8001%2F%2Fclient.php
步驟5 中,認證服務器回覆 HTTP 請求,包含如下參數:
參數名 | 必填 | 說明 |
---|---|---|
access_token | 是 | 訪問令牌 |
token_type | 是 | 令牌類型,能夠是 bearer 或者 mac 類型 |
expire_in | 是 | 表示過時時間,單位爲秒, |
refresh_token | 否 | 用來獲取下一次的令牌訪問 |
scope | 否 | 表示權限範圍 |
例子:
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA" }
Cache-Control
, 表示強制此接口不緩存。
爲了方便的在咱們本身的項目中集成 Oauth2, 能夠使用一個第三方類庫來實現 oauth2-server-php
下面經過一個Demo 來演示oauth2 的整個流程
代碼分爲客戶端、服務端兩個部分:
克隆測試代碼到本地
git clone git@github.com:shanhuhai/oauth2-demo.git cd oauth2-demo composer install
克隆完代碼後, 在 oauth2-demo/example 下面有兩個目錄分別爲 client 和 server 對應客戶端和服務端。
將這兩個目錄在nginx分別綁定 oauth2-client.dev 和 oauth2-server.dev 域名。
建立一個數據庫 ‘my_oauth2_db’, 下載這個sql 文件並導入數據庫中
Oauth2演示用數據表
打開 oauth2-demo/example/server/common.php
配置好數據庫信息。
在你的hosts
文件中,配置
127.0.0.1 oauth2-client.dev 127.0.0.1 oauth2-server.dev
在瀏覽器打開 http://oauth2-client.dev/index.php
,就能夠測試了,
默認提供的測試賬號是 「shanhuhai」, 密碼 「123123」
在代碼註釋中標明瞭業務流程的關鍵點,對應前文列出的主要步驟。
客戶端:
客戶端只有一個文件。
index.php
<!doctype html>
<html lang="zh-CN">
<head>
<title>用戶信息</title>
</head>
<body>
require '../../vendor/autoload.php';
define('CLIENT_URL', 'http://oauth2-client.dev');
define('SERVER_URL', 'http://oauth2-server.dev');
define('REDIRECT_URI', CLIENT_URL.'/index.php');
define('RESOURCE_URL', SERVER_URL.'/resource.php');
define('CLIENT_ID', 'testclient');
define('CLIENT_SECRET', 'testpass');
session_start();
function userInfo(){
if(isset($_SESSION['username'])) {
return $_SESSION;
} else {
return false;
}
}
if(isset($_REQUEST['logout'])) {
unset($_SESSION['username']);
session_destroy();
}
$userInfo = userInfo();
/*
* 接收用戶中心返回的受權碼
*/
if (isset($_REQUEST['code']) && $_SERVER['REQUEST_URI']) {
//將認證服務器返回的受權碼從 URL 中解析出來
$code = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], 'code=')+5, 40);
// 步驟4 拿受權碼去申請令牌
$client = new GuzzleHttp\Client();
$response = $client->request('POST', SERVER_URL.'/token.php', [
'auth' => [CLIENT_ID, CLIENT_SECRET],
'form_params'=> [
'grant_type'=>'authorization_code',
'code'=> $code,
'redirect_uri'=> REDIRECT_URI,
]
]);
$response = json_decode($response->getBody(), true);
// 將令牌緩存到 SESSION中,方便後續訪問
$_SESSION['access_token'] = $response['access_token'];
// 步驟6 使用令牌獲取用戶信息
$response = $client->request('GET', RESOURCE_URL.'?access_token='.$_SESSION['access_token']);
$response = json_decode($response->getBody(), true);
$userInfo = $response['userInfo'];
$_SESSION = array_merge($_SESSION, $userInfo);
}
// 步驟1,點擊此連接跳轉到認證中心
$auth_url = SERVER_URL."/authorize.php?response_type=code&client_id=testclient&state=xyz&redirect_uri=". REDIRECT_URI;
歡迎
<a href="/index.php?logout=1">退出登陸</a>
<a href="<?php echo $auth_url ?>">使用媒體雲登陸</a>
</body>
</html>
服務端:
authorize.php
require_once __DIR__."/common.php";
$_SESSION['authorize_querystring'] = $_SERVER['QUERY_STRING'];
// 步驟2 判斷若是沒有登陸則跳轉到登陸界面
if(!isset($_SESSION['username']) && strpos($_SERVER['REQUEST_URI'], 'login.php') === false) {
header("Location: ".SERVER_URL.'/login.php');
exit;
}
$request = OAuth2\Request::createFromGlobals();
$response = new \OAuth2\Response();
if(!$server->validateAuthorizeRequest($request, $response)) {
$response->send();
die;
}
if(empty($_POST)) {
// 步驟3 ,用戶已經在認證中心登陸,用戶選擇是否開放受權給客戶端
exit('<form method="post">
<label>是否受權給 '.$_GET['client_id'].'?</label><br />
<input type="submit" name="authorized" value="yes">
<input type="submit" name="authorized" value="no">
</form>
<a href="/login.php?logout=1">退出登陸</a>
');
}
// print the authorization code if the user has authorized your client
$is_authorized = ($_POST['authorized'] === 'yes');
$server->handleAuthorizeRequest($request, $response, $is_authorized);
$response->send();
token.php
// 步驟5 ,認證服務器發放令牌
require_once __DIR__ . '/common.php';
$server->handleTokenRequest(OAuth2\Request::createFromGlobals())->send();