form1.cn
Make a little progress every day

PHP实现OAuth2.0服务(入门)

17th of February 2017 PHP PHPcode 2238

在现在的网络服务中,OAuth2.0服务已经很普遍了,无论是facebook或者微博的第三方登录,还是手机APP登录,都有很广泛的应用。

它主要的目的如下:

如果用户的照片在A网站,他想要在B网站使用A网站的头像,并不需要向B网站提供自己在A网站的用户名和密码,而直接给B一个Access Token来获取A站的照片

具体流程如下:

1)用户访问网站B
2)B需要验证用户的身份(需要在B网站登录)
3)B将用户定向到A网站,用户输入帐号密码登录A网站(定向到A网站登录)
4)A网站询问是否要将Authentication的权利给B网站(问用户是否要授权到B网站)
5)用户告诉A站可以将认证权给B站(用户说可以)
6)A网站把Authorization Code发给B站(A网站把一个code给B网站)
7)B站用Autorization Code向A站换取Access Token
8)当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,也可以用下列命令下载,不过内容都是一样的

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(255) NOT NULL, password VARCHAR(2000), first_name VARCHAR(255), last_name VARCHAR(255), 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

$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
// 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控制器


需要先创建一条记录,来注册一个新的应用

INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://fake/");

然后用命令行调用

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的正确

curl http://localhost/resource.php -d 'access_token=YOUR_TOKEN'

如果没出问题,则会得到下面的结果JSON

{"success":true,"message":"You accessed my APIs!"}


认证控制器的创建和测试


验证控制器是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

http://localhost/authorize.php?response_type=code&client_id=testclient&state=xyz


你将会看到一个表单,当你选择yes的时候会弹出你所获得的Authorization Code

现在你可以用这个Authorization Code来刚才建立的token.php获得TOKEN,命令如下

curl -u testclient:testpass http://localhost/token.php -d 'grant_type=authorization_code&code=YOUR_CODE'


就像刚才一样,你获得了一个TOKEN

{"access_token":"6f05ad622a3d32a5a81aee5d73a5826adb8cbf63","expires_in":3600,"token_type":"bearer","scope":null}

请在30秒内完成这个操作,因为Authorization Code的有效期只有30秒


用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']}";