使用OAuth-Server-PHP实现OAuth2服务
OAuth 原理
思路
- OAuth在”客户端”与”服务提供商”之间,设置了一个授权层(authorization layer)。”客户端”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端”登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。
- “客户端”登录授权层以后,”服务提供商”根据令牌的权限范围和有效期,向”客户端”开放用户储存的资料。
运作流程
- (A)用户打开客户端以后,客户端要求用户给予授权。
- (B)用户同意给予客户端授权。
- (C)客户端使用上一步获得的授权,向认证服务器申请令牌。
- (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
- (E)客户端使用令牌,向资源服务器申请获取资源。
- (F)资源服务器确认令牌无误,同意向客户端开放资源
四种模式
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
代码实现
介绍
在现在的网络服务中,OAuth2.0服务已经很普遍了,无论是facebook或者微博的第三方登录,还是手机APP登录,都有很广泛的应用。 它主要的目的如下: 如果用户的照片在A网站,他想要在B网站使用A网站的头像,并不需要向B网站提供自己在A网站的用户名和密码,而直接给B一个Access Token来获取A站的照片 具体流程如下:
- 用户访问网站B
- B需要验证用户的身份
- B将用户定向到A网站,用户输入帐号密码登录A网站
- A网站询问是否要将Authentication的权利给B网站
- 用户告诉A站可以将认证权给B站
- A网站把Authorization Code发给B站
- B站用Autorization Code向A站换取Access Token
- 当B站拥有Access Token时,就拥有了用户在A站的一些访问权限 这是典型的Authorization Code Grant,常常运用于网络应用之中
还有Implicit Grant认证方式,这个则省去了Auth Code,开放平台直接返回access_token和有效期,用户ID等数据 这种经常运用于手机客户端或者浏览器插件等没有在线服务器的应用
最后一种是Resource Owner Password Credentials Grant 这种是直接在应用中输入帐号密码,然后由应用XAuth技术将其提交给开放平台并得到Access Token 它经常用于PC可执行程序和手机应用,但由于存在一些争议,开发难度也较大,这里我就先不讨论他
安装
你可以在github上下载OAuth Server PHP,也可以用下列命令下载,不过内容都是一样的
Shell
mkdir my-oauth2-walkthrough
cd my-oauth2-walkthrough
git clone https://github.com/bshaffer/oauth2-server-php.git -b master
在这之后配置数据库
CREATE TABLE oauth_clients (client_id VARCHAR(80) NOT NULL, client_secret VARCHAR(80) NOT NULL, redirect_uri VARCHAR(2000) NOT NULL, grant_types VARCHAR(80), scope VARCHAR(100), user_id VARCHAR(80), CONSTRAINT clients_client_id_pk PRIMARY KEY (client_id));
CREATE TABLE oauth_access_tokens (access_token VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(255), expires TIMESTAMP NOT NULL, scope VARCHAR(2000), CONSTRAINT access_token_pk PRIMARY KEY (access_token));
CREATE TABLE oauth_authorization_codes (authorization_code VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(255), redirect_uri VARCHAR(2000), expires TIMESTAMP NOT NULL, scope VARCHAR(2000), CONSTRAINT auth_code_pk PRIMARY KEY (authorization_code));
CREATE TABLE oauth_refresh_tokens (refresh_token VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(255), expires TIMESTAMP NOT NULL, scope VARCHAR(2000), CONSTRAINT refresh_token_pk PRIMARY KEY (refresh_token));
CREATE TABLE oauth_users (username VARCHAR(90) NOT NULL, password VARCHAR(2000), first_name VARCHAR(90), last_name VARCHAR(90), CONSTRAINT username_pk PRIMARY KEY (username));
CREATE TABLE oauth_scopes (scope TEXT, is_default BOOLEAN);
CREATE TABLE oauth_jwt (client_id VARCHAR(80) NOT NULL, subject VARCHAR(80), public_key VARCHAR(2000), CONSTRAINT jwt_client_id_pk PRIMARY KEY (client_id));
配置
我们来建立一个server.php文件来配置server,这个文件可以被所有的终端来调用。看require once就知道这个文件是平级的。
server.php(模拟的是连接oauth-server的配置文件)
$dsn = 'mysql:dbname=my_oauth2_db;host=localhost';
$username = 'root';
$password = '';
// error reporting (this is a demo, after all!)
ini_set('display_errors',1);error_reporting(E_ALL);
// Autoloading (composer is preferred, but for this example let's just do this)
require_once('oauth2-server-php/src/OAuth2/Autoloader.php');
OAuth2\Autoloader::register();
// $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost"
$storage = new OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));
// Pass a storage object or array of storage objects to the OAuth2 server class
$server = new OAuth2\Server($storage);
// Add the "Client Credentials" grant type (it is the simplest of the grant types)
$server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage));
// Add the "Authorization Code" grant type (this is where the oauth magic happens)
$server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage));
最后记得配置数据库PDO的用户名和密码
Token控制器
下面,我们将建立一个Token控制器,这个控制器URI将会返回OAuth2的Token给客户端
token.php(用于来发送access-token的文件)
// include our OAuth2 Server object
require_once __DIR__.'/server.php';
// Handle a request for an OAuth2.0 Access Token and send the response to the client
$server->handleTokenRequest(OAuth2\Request::createFromGlobals())->send();
测试Token控制器
SQL(相当于创建一个客户端)
INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://fake/");
然后用命令行调用
shell
curl -u testclient:testpass http://localhost/token.php -d 'grant_type=client_credentials'
这里的URL只是示例,实地操作要确定能找到这个token.php 如果运行正常,则显示
Json
{"access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35","expires_in":3600,"token_type":"bearer","scope":null}
资源控制器的建立和测试
你创建了Token,你需要在API中测试它,于是你写了如下代码
resource.php(相当于要访问的资源,在访问之前要验证权限)
// include our OAuth2 Server object
require_once __DIR__.'/server.php';
// Handle a request for an OAuth2.0 Access Token and send the response to the client
if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
$server->getResponse()->send();
die;
}
echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));
然后运行下面的命令,记得将YOUR_TOKEN替换成刚才得到的token,还有确保URL的正确
Shell
curl http://localhost/resource.php -d 'access_token=YOUR_TOKEN'
如果没出问题,则会得到下面的结果
Json
{"success":true,"message":"You accessed my APIs!"}
至此,客户端获取access-token的代码和测试均已经实现,为模式四grant_type=client_credentials。下面是认证的另一个模式测试,模式一授权码模式。
认证控制器的创建和测试
验证控制器是OAuth2的杀手锏,它允许你的平台帮助用户验证第三方应用 它不像第一个例子中直接返回一个Access Token,这里稍微复杂一点
authorize.php(认证授权的页面,可获取授权码)
// include our OAuth2 Server object
require_once __DIR__.'/server.php';
$request = OAuth2\Request::createFromGlobals();
$response = new OAuth2\Response();
// validate the authorize request
if (!$server->validateAuthorizeRequest($request, $response)) {
$response->send();
die;
}
// display an authorization form
if (empty($_POST)) {
exit('
<form method="post">
<label>Do You Authorize TestClient?</label><br />
<input type="submit" name="authorized" value="yes">
<input type="submit" name="authorized" value="no">
</form>');
}
// print the authorization code if the user has authorized your client
$is_authorized = ($_POST['authorized'] === 'yes');
$server->handleAuthorizeRequest($request, $response, $is_authorized);
if ($is_authorized) {
// this is only here so that you get to see your code in the cURL request. Otherwise, we'd redirect back to the client
$code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5, 40);
exit("SUCCESS! Authorization Code: $code");
}
$response->send();
然后在浏览器中打开这个URL
Url
http://localhost/authorize.php?response_type=code&client_id=testclient&state=xyz
你将会看到一个表单,当你选择yes的时候会弹出你所获得的Authorization Code 现在你可以用这个Authorization Code来刚才建立的token.php获得TOKEN,命令如下
Shell
curl -u testclient:testpass http://localhost/token.php -d 'grant_type=authorization_code&code=YOUR_CODE'
就像刚才一样,你获得了一个TOKEN
json
{"access_token":"6f05ad622a3d32a5a81aee5d73a5826adb8cbf63","expires_in":3600,"token_type":"bearer","scope":null}
请在30秒内完成这个操作,因为Authorization Code的有效期只有30秒
获得了acess-token之后,利用之前的resource.php访问资源验证正确性
用Access Token联系本地用户
当你认证了一个用户并且分派了一个Token之后,你可能想知道彼时到底是哪个用户使用了这个Token 你可以使用handleAuthorizeRequest的可选参数user_id来完成,修改你的authorize.php文件
authorize.php
$userid = 1234; // A value on your server that identifies the user
$server->handleAuthorizeRequest($request, $response, $is_authorized, $userid);
这样一来,用户ID就伴随Token一起存进数据库了 当Token被客户端使用的时候,你就知道是哪个用户了,修改resource.php来完成任务
resource.php
if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
$server->getResponse()->send();
die;
}
$token = $server->getAccessTokenData(OAuth2\Request::createFromGlobals());
echo "User ID associated with this token is {$token['user_id']}";
php curl部分可以根据设置登录的用户名和密码
curl_setopt($ch,CURLOPT_USERPWD,"testclient:testpass");
见网盘2017-8-15/php-oauth