转自: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();