跳转至

再说Oauth

真跟Oauth扯不清关系了,几个月不见,就又得有求于她。不过这次我要说的是Oauth Server。 由于项目需求,要给其他的平台还有合作伙伴提供数据支持,这就涉及到平台接口的编写。那么用什么样的平台接口呢?

  • 自己写一套,费时费力,还可能不安全。PASS
  • 利用目前广为流行的WebService,这应该是个不错的选择。不过有一定的平台局限性,还有操作起来还很复杂,需要跟中扩展组件的支持。PASS
  • REST,足够简单,会Web开发就可以用REST,而且不需要添加而外的扩展包,因为REST就是HTTP协议的一部分。对客户的要求也很低。OK
  • 而REST公开共用资源的时候,没有问题,可是对于受限资源,需要加上身份验证。而配合REST最佳的身份验证,就是今天的主角,Oauth。

Oauth也是利用HTTP协议,所以也不需要任何三方扩展。 目前广为使用的Oauh有两个版本,一个是1.0a,一个是2.0.根据喜新厌旧的原则,当然要用2.0了,另外1.0a的客户端处理方式都相当复杂,我都不敢考虑他的服务器端了。 Oauth的核心,其实就是那个accesstoken.验证客户身份的时候,就是为了得到这个accesstoken.而accesstoken和现实技术中另一个东西很像,那就是sessionid.其实accesstoken完全可以由sessionid替换。只是Oauth需要面对不同的客户端,所以不能沿用现有的session_id机制。 常见的Oauth的授权验证方式有两种:

  • 一种就是 Client Credentials 客户端验证方式 此种方式,oauth client无须得到user的授权,便可以直接向oauth server获取数据。 image

客户端的认证 如图,客户端首先使用Oauth server预先分配的username和password(有些地方可能叫做AppID和AppKey)向服务器请求access_token.(通常接受这个请求的地址叫做token节点)。服务器验证客户端身份之后,给分配一个临时的access_token.通常access_token是有时限的,这点和session非常类似。

接下来,客户端利用得到的access_token向REST server请求资源。在REST server相应请求之前,需要Oauth server服务器验证access_token是否有效,有没有过期。所以我把REST server放在Oauth server的后面。验证通过之后,REST server就可以相应客户端的请求了。

看,是不是很简单。其中access_token是服务器生成的一个随机变量,并且和client做了绑定。服务器再接受到此access_token的时候,就可以查询到请求的client了。

  • 接下来,是另一种授权方式 Authorization Code 用户授权 image

这个看上去可能有些复杂,其实主要是有这么几步。

  • 用户要请求在其他地方的资源(Oauth server)
  • 客户端接过用户的请求,加上自己的client username去请求Oauth server的授权
  • Oauth server返回一个授权页面,也就是图中第3步,如果用户确认将资源的访问权交给客户端,那么服务器就给客户端返回一个authorization_code。另外在Oauth server返回确认页面之前要现验证用户是否登录。
  • 客户端根据得到authorizationcode,加上自己的username和password,再向oauth server请求accesstoken.得到access_token之后。就可以访问用户的资源了。

整个Oauth就是这么简单。通常oauth还有一个scope的参数,用于细粒度的控制访问权限。关于安全性,client向服务器发送带有password的请求的时候,都是服务器和服务器之间的通讯,所以被监听的概率很低,另外即使拦截了,请求一般也是通过SSL加密过的,解析出来也很困难。

接下来是针对PHP的实战演练,有兴趣的可以继续看下去,如果不是采用PHP进行编码的话,也可以在这里

oauth2指导网站找到适合自己的客户端和者服务器端的类库。 用到的PHP类库可以在github找到,下面所展示的内容也主要是翻译,原文可以在Step-By-Step Walkthrough找到。

  • 初始化一下项目 这个地方是创建个目录,然后把这个类库clone下来
mkdir my-oauth2-walkthrough 
cd my-oauth2-walkthrough 
git clone https://github.com/bshaffer/oauth2-server-php.git 
  • 创建数据库
CREATE TABLE oauth_clients (client_id VARCHAR(80) NOT NULL, client_secret VARCHAR(80) NOT NULL, redirect_uri VARCHAR(2000) NOT NULL, CONSTRAINT 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));

直接执行一下上面的语句就可以了,如果有额外需求,可以再修改。

  • 开始搭建你自己的Oauth server基础代码 下面是创建还有配置Oauth server对象。程序的其他地方都会引用这个对象。把文件命名server.php
$dsn = 'mysql:dbname=my_oauth2_db;host=localhost'; 
$username = 'root'; 
$password = ''; 
//为了方便测试,所有开启全部错误报告 
ini_set('display_errors',1);error_reporting(E_ALL); 

// 这是一个Autoloader,用来帮助自动载入需要的文件。如果不闲麻烦,自己手动载入也是可以的。 
require_once('oauth2-server-php/src/OAuth2/Autoloader.php'); 
OAuth2\Autoloader::register(); 

// 创建一个数据存储对象。这里用的是SQL,当然还有其它的方式可供选择,比如Mongodb,Memcached。或者你自定义的。 
$storage = new OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));

// 然后用刚才创建创建的存储对象初始化Oauth server
$server = new OAuth2\Server($storage); 

// 给Oauth server对象增加客户端授权方式
$server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage)); 
// 给Oauth server对象增加用户授权方式
$server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage));
  • 创建Token Controller 创建一个Token Controller.它的作用在于给客户端创建一个access_token.把文件命名成token.php
// 引入刚才创建的server.php
require_once <strong>DIR</strong>.'/server.php';

// 下面的代码用于接受并处理access_token请求
$server->handleTokenRequest(OAuth2\Request::createFromGlobals())->send();

创建好token controller之后,要向数据库插入一条客户端的测试数据,

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

好了,准备工作完成了,下面请求一个access_token试试

curl -u testclient:testpass http://localhost/token.php -d 'grant_type=client_credentials'

别告诉我你不知道curl。这是linux下面一个非常强大的工具,赶紧加入linux家族吧,早入早超生^v^

然后你会得到类似于下面的一条结果

{"access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35","expires_in":3600,"token_type":"bearer","scope":null}

这说明一切运行正常,你得到了一个access_token

  • 接下来创建一个Resource Controller

这是一个提供资源服务的controller.也就是通常是REST服务。把这个文件命名resource.php

// 也要引入server.php文件安
require_once <strong>DIR</strong>.'/server.php';

// 接受请求,并验证是否通过授权
if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
    $server->getResponse()->send();
    die;
}
echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));

再次拿出神奇curl来验证以下

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

YOUR_TOKEN换成刚才得到的access_token

如果成功了,那么你将得到下面的结果

{"success":true,"message":"You accessed my APIs!"}
  • 创建Authorize Controller

创建authorize controller之后,就可以相应用户的授权请求了。这是最常用的部分。文件为authorize.php

// 引入server.php
require_once __DIR__.'/server.php';

$request = OAuth2\Request::createFromGlobals();
$response = new OAuth2\Response();

// 验证请求是否合法 (!$server->validateAuthorizeRequest($request, $response)) {
    $response->send();
    die;
}
// 如果合法的话,输出一个确认授权对话框
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>');
}

// 如果用户确认了授权给客户端,那么输出一个authorize<em>code
$is</em>authorized = ($<em>POST['authorized'] === 'yes');
$server->handleAuthorizeRequest($request, $response, $is</em>authorized);
if ($is<em>authorized) {
  // 这就是为了让你看到authorize</em>code. 常规情况的话,会之际把authorize_code返回给客户端
  $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5, 40);
  exit("SUCCESS! Authorization Code: $code");
}
$response->send();

然后来测试一下这个功能,在浏览器里面访问下面的地址

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

然后得要点击yes了。否则就授权不了。

然后要用authorize_code去交换一个access_token。 上神器

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

这里操作要快,要最快,因为authorize_code只有30秒的有效期哦。

正确结果是这样的

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

PS:哪个同学能给我指条明路,wordpress下用markdown怎么才能和syntaxhighlighter配合好?