天天看点

API接口设计 OAuth2.0认证

<a href="http://oauth.net/2/">oauth2.0官网</a>

API接口设计 OAuth2.0认证

git clone https://github.com/bshaffer/oauth2-server-php.git  

oauth和openid的区别:

oauth关注的是authorization授权,即:'用户能做什么';而openid侧重的是authentication认证,即:'用户是谁'。openid是用来认证协议,oauth是授权协议,二者是互补的

oauth 2.0将分为两个角色: authorization server负责获取用户的授权并且发布token; resource负责处理api calls。

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

具体流程如下:

1)用户访问网站b

2)b需要验证用户的身份

3)b将用户定向到a网站,用户输入帐号密码登录a网站

4)a网站询问是否要将authentication的权利给b网站

5)用户告诉a站可以将认证权给b站

6)a网站把authorization code发给b站

7)b站用autorization code向a站换取access token

8)当b站拥有access token时,就拥有了用户在a站的一些访问权限

这是典型的authorization code grant,常常运用于网络应用之中

还有implicit grant认证方式,这个则省去了authcode,开放平台直接返回access_token和有效期,用户id等数据这种经常运用于手机客户端或者浏览器插件等没有在线服务器的应用

最后一种是resource owner password credentials grant

这种是直接在应用中输入帐号密码,然后由应用xauth技术将其提交给开放平台并得到access token

它经常用于pc可执行程序和手机应用,但由于存在一些争议,开发难度也较大,这里我就先不讨论他

使用 oauth2-server-php

API接口设计 OAuth2.0认证

create table `oauth_access_tokens` (  

`access_token` varchar(40) not null,  

`client_id` varchar(80) not null,  

`user_id` varchar(255) default null,  

`expires` timestamp not null default current_timestamp on update current_timestamp,  

`scope` varchar(2000) default null,  

primary key (`access_token`)  

) engine=innodb default charset=utf8;  

create table `oauth_authorization_codes` (  

`authorization_code` varchar(40) not null,  

`redirect_uri` varchar(2000) default null,  

primary key (`authorization_code`)  

create table `oauth_clients` (  

`client_secret` varchar(80) not null,  

`redirect_uri` varchar(2000) not null,  

`grant_types` varchar(80) default null,  

`scope` varchar(100) default null,  

`user_id` varchar(80) default null,  

primary key (`client_id`)  

create table `oauth_refresh_tokens` (  

`refresh_token` varchar(40) not null,  

primary key (`refresh_token`)  

create table `oauth_users` (  

`user_id` int(11) not null auto_increment,  

`username` varchar(255) not null,  

`password` varchar(2000) default null,  

`first_name` varchar(255) default null,  

`last_name` varchar(255) default null,  

primary key (`user_id`)  

) engine=innodb auto_increment=2 default charset=utf8;  

create table `oauth_scopes` (  

  `scope` text,  

  `is_default` tinyint(1) default null  

create table `oauth_jwt` (  

  `client_id` varchar(80) not null,  

  `subject` varchar(80) default null,  

  `public_key` varchar(2000) default null,  

  primary key (`client_id`)  

-- test data  

insert into oauth_clients (client_id, client_secret, redirect_uri) values ("testclient", "testpass", "http://www.baidu.com/");  

insert into oauth_users (username, password, first_name, last_name) values ('rereadyou', '8551be07bab21f3933e8177538d411e43b78dbcc', 'bo', 'zhang');  

我们来建立一个server.php文件来配置server,这个文件可以被所有的终端来调用。看require once就知道这个文件是平级的

API接口设计 OAuth2.0认证

&lt;?php  

$dsn = 'mysql:dbname=test;host=localhost';  

$username = 'root';  

$password = 'orbit';  

// 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' =&gt; $dsn, 'username' =&gt; $username, 'password' =&gt; $password));  

// pass a storage object or array of storage objects to the oauth2 server class  

//$server = new oauth2\server($storage);  

$server = new oauth2\server($storage, array(  

    'allow_implicit' =&gt; true,  

    'refresh_token_lifetime'=&gt; 2419200,  

));  

// add the "client credentials" grant type (it is the simplest of the grant types)  

$server-&gt;addgranttype(new oauth2\granttype\clientcredentials($storage));  

// add the "authorization code" grant type (this is where the oauth magic happens)  

$server-&gt;addgranttype(new oauth2\granttype\authorizationcode($storage));  

//resource owner password credentials (资源所有者密码凭证许可)  

$server-&gt;addgranttype(new oauth2\granttype\usercredentials($storage));  

//can refreshtoken set always_issue_new_refresh_token=true  

$server-&gt;addgranttype(new oauth2\granttype\refreshtoken($storage, array(  

    'always_issue_new_refresh_token' =&gt; true  

)));  

// configure your available scopes  

$defaultscope = 'basic';  

$supportedscopes = array(  

    'basic',  

    'postonwall',  

    'accessphonenumber'  

);  

$memory = new oauth2\storage\memory(array(  

    'default_scope' =&gt; $defaultscope,  

    'supported_scopes' =&gt; $supportedscopes  

$scopeutil = new oauth2\scope($memory);  

$server-&gt;setscopeutil($scopeutil);  

下面,我们将建立一个token控制器,这个控制器uri将会返回oauth2的token给客户端

API接口设计 OAuth2.0认证

require_once __dir__.'/server.php';  

// handle a request for an oauth2.0 access token and send the response to the client  

$server-&gt;handletokenrequest(oauth2\request::createfromglobals())-&gt;send();  

client credentials grant (客户端凭证许可)

API接口设计 OAuth2.0认证

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

如果运行正常,则显示

API接口设计 OAuth2.0认证

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

你创建了token,你需要在api中测试它,于是你写了如下代码

API接口设计 OAuth2.0认证

// include our oauth2 server object  

require_once __dir__ . '/server.php';  

if (!$server-&gt;verifyresourcerequest(oauth2\request::createfromglobals())) {  

    $server-&gt;getresponse()-&gt;send();  

    die;  

}  

echo json_encode(array('success' =&gt; true, 'message' =&gt; 'you accessed my apis!'));  

 然后运行下面的命令,记得将your_token替换成刚才得到的token,还有确保url的正确

API接口设计 OAuth2.0认证

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

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

API接口设计 OAuth2.0认证

{"success":true,"message":"you accessed my apis!"}  

authorization code grant (授权码认证)

API接口设计 OAuth2.0认证

$request = oauth2\request::createfromglobals();  

$response = new oauth2\response();  

// validate the authorize request  

if (!$server-&gt;validateauthorizerequest($request, $response)) {  

    $response-&gt;send();  

// display an authorization form  

if (empty($_post)) {  

    exit('  

&lt;form method="post"&gt;  

  &lt;label&gt;do you authorize testclient?&lt;/label&gt;&lt;br /&gt;  

  &lt;input type="submit" name="authorized" value="yes"&gt;  

&lt;/form&gt;');  

// print the authorization code if the user has authorized your client  

$is_authorized = ($_post['authorized'] === 'yes');  

$server-&gt;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-&gt;gethttpheader('location'), strpos($response-&gt;gethttpheader('location'), 'code=') + 5, 40);  

    //exit("success and do redirect_uri! authorization code: $code");  

$response-&gt;send();  

然后在浏览器中打开这个url

API接口设计 OAuth2.0认证

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

你将会看到一个表单,当你选择yes的时候会弹出你所获得的authorization code现在你可以用这个authorization code来刚才建立的token.php获得token,命令如下

API接口设计 OAuth2.0认证

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

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

API接口设计 OAuth2.0认证

{"access_token":"6ec6afa960587133d435d67d31e8ac08efda65ff","expires_in":3600,"token_type":"bearer","scope":null,"refresh_token":"e57fafaa693a998b302ce9ec82d940d7325748d3"}  

请在30秒内完成这个操作,因为authorizationcode的有效期只有30秒,可以修改 oauth2/responsetype/authorizationcode.php 中的 authorizationcode class 的构造方法配置参数来自定义 authorization_code 有效时间.

access_token 有效期为3600s, refresh_token 有效期为 1209600s,可以在oauth2/responsetype/accesstoken.php 中的 accesstoken class 中的构造函数配置中进行修改。

可修改 oauth2/granttype/refreshtoken.php 中的 refreshtoken class __construct 方法中的 'always_issue_new_refresh_token' =&gt; true 来开启颁发新的 refresh_token.使用 refresh_token 换取 access_token:首先,刷新令牌必须使用授权码或资源所有者密码凭证许可类型检索:

API接口设计 OAuth2.0认证

curl -u testclient:testpass http:://localhost/token.php -d 'grant_type=refresh_token&amp;refresh_token=your_refresh_token'  

资源所有者密码凭证许可: user 表设计使用 sha1 摘要方式,没有添加 salt.在 pdo.php中有protected function checkpassword($user, $password)

API接口设计 OAuth2.0认证

curl -u testclient:testpass http://localhost/token.php -d 'grant_type=password&amp;username=rereadyou&amp;password=rereadyou'  

当你认证了一个用户并且分派了一个token之后,你可能想知道彼时到底是哪个用户使用了这个token

你可以使用handleauthorizerequest的可选参数user_id来完成,修改你的authorize.php文件

API接口设计 OAuth2.0认证

$userid = 1; // a value on your server that identifies the user  

$server-&gt;handleauthorizerequest($request, $response, $is_authorized, $userid);  

这样一来,用户id就伴随token一起存进数据库了当token被客户端使用的时候,你就知道是哪个用户了,修改resource.php来完成任务

API接口设计 OAuth2.0认证

}   

$token = $server-&gt;getaccesstokendata(oauth2\request::createfromglobals());  

echo "user id associated with this token is {$token['user_id']}";  

scope 需要服务端确定具体的可行操作。

API接口设计 OAuth2.0认证

curl -u testclient:testpass http://localhost/token.php -d 'grant_type=client_credentials&amp;scope=postonwall'  

scope 用来确定 client 所能进行的操作权限。项目中操作权限由 srbac 进行控制, oauth2 中暂不做处理

API接口设计 OAuth2.0认证

$scoperequired = 'postonwall'; // this resource requires "postonwall" scope  

if (!$server-&gt;verifyresourcerequest($request, $response, $scoperequired)) {  

    // if the scope required is different from what the token allows, this will send a "401 insufficient_scope" error  

state 为 client app 在第一步骤中获取 authorization code 时向 oauth2 server 传递并由 oauth2 server 返回的随机哈希参数。state 参数主要用来防止跨站点请求伪造

如果对整个调用请求中的参数进行排序,再以随机字符串nonce_str和timestamp加上排序后的参数来对整个调用生成1个sign,黑客即使截获sign,不同的时间点、参数请求所使用的sign也是不同的,难以伪造,自然会更安全。当然,写起来也更费事。加入随机字符串nonce_str主要保证签名不可预测。

API接口设计 OAuth2.0认证

$sign = new signgenerator($params);  

$sign-&gt;onsortafter(function($that) use($key) {  

    $that-&gt;key = $key;  

});  

$params['sign'] = $sign-&gt;getresult();