oauth2-server-php for windows 的那些坑

在windwos 环境下,使用vs2017 for php 工具进行调试时,总是搞不出来,

于是分析了一下原因,

首先,oauth2-server-php的环境是linux 而我在windows 环境下,步骤上是有些区别,但也不至于没办法调试

下面就分析一下每个步骤的细微区别“

1、windows 源码地址是,https://github.com/bshaffer/oauth2-server-php/releases 下载最新版本的,基本类同,

  1. https://github.com/bshaffer/oauth2-server-php/releases

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

  1. CREATE TABLE `oauth_access_tokens` (
  2. `access_token` varchar(40) NOT NULL,
  3. `client_id` varchar(80) NOT NULL,
  4. `user_id` varchar(255) DEFAULT NULL,
  5. `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  6. `scope` varchar(2000) DEFAULT NULL,
  7. PRIMARY KEY (`access_token`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  9.  
  10. CREATE TABLE `oauth_authorization_codes` (
  11. `authorization_code` varchar(40) NOT NULL,
  12. `client_id` varchar(80) NOT NULL,
  13. `user_id` varchar(255) DEFAULT NULL,
  14. `redirect_uri` varchar(2000) DEFAULT NULL,
  15. `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  16. `scope` varchar(2000) DEFAULT NULL,
  17. PRIMARY KEY (`authorization_code`)
  18. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  19.  
  20. CREATE TABLE `oauth_clients` (
  21. `client_id` varchar(80) NOT NULL,
  22. `client_secret` varchar(80) NOT NULL,
  23. `redirect_uri` varchar(2000) NOT NULL,
  24. `grant_types` varchar(80) DEFAULT NULL,
  25. `scope` varchar(100) DEFAULT NULL,
  26. `user_id` varchar(80) DEFAULT NULL,
  27. PRIMARY KEY (`client_id`)
  28. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  29.  
  30. CREATE TABLE `oauth_refresh_tokens` (
  31. `refresh_token` varchar(40) NOT NULL,
  32. `client_id` varchar(80) NOT NULL,
  33. `user_id` varchar(255) DEFAULT NULL,
  34. `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  35. `scope` varchar(2000) DEFAULT NULL,
  36. PRIMARY KEY (`refresh_token`)
  37. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  38.  
  39. CREATE TABLE `oauth_users` (
  40. `user_id` int(11) NOT NULL AUTO_INCREMENT,
  41. `username` varchar(255) NOT NULL,
  42. `password` varchar(2000) DEFAULT NULL,
  43. `first_name` varchar(255) DEFAULT NULL,
  44. `last_name` varchar(255) DEFAULT NULL,
  45. PRIMARY KEY (`user_id`)
  46. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  47.  
  48. CREATE TABLE `oauth_scopes` (
  49. `scope` text,
  50. `is_default` tinyint(1) DEFAULT NULL
  51. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  52.  
  53. CREATE TABLE `oauth_jwt` (
  54. `client_id` varchar(80) NOT NULL,
  55. `subject` varchar(80) DEFAULT NULL,
  56. `public_key` varchar(2000) DEFAULT NULL,
  57. PRIMARY KEY (`client_id`)
  58. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  59. -- test data
  60. INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://www.baidu.com/");
  61. INSERT INTO oauth_users (username, password, first_name, last_name) VALUES ('rereadyou', '8551be07bab21f3933e8177538d411e43b78dbcc', 'bo', 'zhang');

各表的名字说明了表中存取的内容,表名可自定义,自定义位置为:OAuth2/Storage/Pdo.php 74行的 config 数组中,因为这里采用的是 mysql 数据库,所以需要修改的是 Pdo,若是采用其它的存储方案,如 Redis,则自行修改对应文件即可。注意这里的数据库名称是都是单数形式

/Pdo.php

  1. // debugging
  2. $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
  3.  
  4. $this->config = array_merge(array(
  5. 'client_table' => 'oauth_clients',
  6. 'access_token_table' => 'oauth_access_tokens',
  7. 'refresh_token_table' => 'oauth_refresh_tokens',
  8. 'code_table' => 'oauth_authorization_codes',
  9. 'user_table' => 'oauth_users',
  10. 'jwt_table' => 'oauth_jwt',
  11. 'jti_table' => 'oauth_jti',
  12. 'scope_table' => 'oauth_scopes',
  13. 'public_key_table' => 'oauth_public_keys',
  14. ), $config);

配置

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

  1. <?php
  2. $dsn = 'mysql:dbname=test;host=localhost';
  3. $username = 'root';
  4. $password = 'orbit';
  5.  
  6. // error reporting (this is a demo, after all!)
  7. ini_set('display_errors', 1);
  8. error_reporting(E_ALL);
  9.  
  10. // Autoloading (composer is preferred, but for this example let's just do this)
  11. require_once('oauth2-server-php/src/OAuth2/Autoloader.php');
  12. OAuth2\Autoloader::register();
  13.  
  14. // $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost"
  15. $storage = new OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));
  16.  
  17. // Pass a storage object or array of storage objects to the OAuth2 server class
  18. //$server = new OAuth2\Server($storage);
  19. $server = new OAuth2\Server($storage, array(
  20. 'allow_implicit' => true,
  21. 'refresh_token_lifetime'=> 2419200,
  22. ));
  23. // Add the "Client Credentials" grant type (it is the simplest of the grant types)
  24. $server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage));
  25.  
  26. // Add the "Authorization Code" grant type (this is where the oauth magic happens)
  27. $server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage));
  28. //Resource Owner Password Credentials (资源所有者密码凭证许可)
  29. $server->addGrantType(new OAuth2\GrantType\UserCredentials($storage));
  30. //can RefreshToken set always_issue_new_refresh_token=true
  31. $server->addGrantType(new OAuth2\GrantType\RefreshToken($storage, array(
  32. 'always_issue_new_refresh_token' => true
  33. )));
  34. // configure your available scopes
  35. $defaultScope = 'basic';
  36. $supportedScopes = array(
  37. 'basic',
  38. 'postonwall',
  39. 'accessphonenumber'
  40. );
  41. $memory = new OAuth2\Storage\Memory(array(
  42. 'default_scope' => $defaultScope,
  43. 'supported_scopes' => $supportedScopes
  44. ));
  45. $scopeUtil = new OAuth2\Scope($memory);
  46. $server->setScopeUtil($scopeUtil);

Token控制器

下面,我们将建立一个Token控制器,这个控制器URI将会返回OAuth2的Token给客户端

  1. <?php
  2. require_once __DIR__.'/server.php';
  3.  
  4. // Handle a request for an OAuth2.0 Access Token and send the response to the client
  5. $server->handleTokenRequest(OAuth2\Request::createFromGlobals())->send();

测试Token控制器

Client Credentials Grant (客户端凭证许可)

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

如果运行正常,则显示

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

资源控制器的建立和测试

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

  1. <?php
  2. // include our OAuth2 Server object
  3. require_once __DIR__ . '/server.php';
  4.  
  5. // Handle a request for an OAuth2.0 Access Token and send the response to the client
  6. if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
  7. $server->getResponse()->send();
  8. die;
  9. }
  10. echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));

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

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

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

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

Authorization Code Grant (授权码认证)

Authorize.php代码

  1. <?php
  2. // include our OAuth2 Server object
  3. require_once __DIR__ . '/server.php';
  4.  
  5. $request = OAuth2\Request::createFromGlobals();
  6. $response = new OAuth2\Response();
  7.  
  8. // validate the authorize request
  9. if (!$server->validateAuthorizeRequest($request, $response)) {
  10. $response->send();
  11. die;
  12. }
  13. // display an authorization form
  14. if (empty($_POST)) {
  15. exit('
  16. <form method="post">
  17. <label>Do You Authorize TestClient?</label><br />
  18. <input type="submit" name="authorized" value="yes">
  19. </form>');
  20. }
  21.  
  22. // print the authorization code if the user has authorized your client
  23. $is_authorized = ($_POST['authorized'] === 'yes');
  24. $server->handleAuthorizeRequest($request, $response, $is_authorized);
  25. if ($is_authorized) {
  26. // this is only here so that you get to see your code in the cURL request. Otherwise, we'd redirect back to the client
  27. $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=') + 5, 40);
  28. //exit("SUCCESS AND DO redirect_uri! Authorization Code: $code");
  29. }
  30. $response->send();

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

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

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

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

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

  1. {"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' => true 来开启颁发新的 refresh_token.使用 refresh_token 换取 access_token:首先,刷新令牌必须使用授权码或资源所有者密码凭证许可类型检索:

  1. curl -u testclient:testpass http:://localhost/token.php -d 'grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN'

资源所有者密码凭证许可: user 表设计使用 sha1 摘要方式,没有添加 salt.

在 Pdo.php中有protected function checkPassword($user, $password)

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

用Access Token联系本地用户

当你认证了一个用户并且分派了一个Token之后,你可能想知道彼时到底是哪个用户使用了这个Token
你可以使用handleAuthorizeRequest的可选参数user_id来完成,修改你的authorize.php文件

  1. $userid = 1; // A value on your server that identifies the user
  2. $server->handleAuthorizeRequest($request, $response, $is_authorized, $userid);

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

  1. if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
  2. $server->getResponse()->send();
  3. die;
  4. }
  5. $token = $server->getAccessTokenData(OAuth2\Request::createFromGlobals());
  6. echo "User ID associated with this token is {$token['user_id']}";

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

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

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

  1. <?php
  2. // include our OAuth2 Server object
  3. require_once __DIR__ . '/server.php';
  4.  
  5. $request = OAuth2\Request::createFromGlobals();
  6. $response = new OAuth2\Response();
  7. $scopeRequired = 'postonwall'; // this resource requires "postonwall" scope
  8. if (!$server->verifyResourceRequest($request, $response, $scopeRequired)) {
  9. // if the scope required is different from what the token allows, this will send a "401 insufficient_scope" error
  10. $server->getResponse()->send();
  11. die;
  12. }
  13. echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));

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

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

  1. $sign = new SignGenerator($params);
  2. $sign->onSortAfter(function($that) use($key) {
  3. $that->key = $key;
  4. });
  5. $params['sign'] = $sign->getResult();

oauth2-server-php for windows 的那些坑 (研究中...)的更多相关文章

  1. 困扰多日的C#调用Haskell问题竟然是Windows的一个坑

    最近一直被C#调用Haskell时的“尝试读取或写入受保护的内存”问题所困扰(详见C#调用haskell遭遇Attempted to read or write protected memory,C# ...

  2. 第一次使用docker for windows 遇到的坑

    原文:第一次使用docker for windows 遇到的坑 1. 目前win10安装docker, 不需要安装其他工具,可直接到官网下载 2. 此版本的docker可同时运行Windows con ...

  3. windows container 踩坑记

    windows container 踩坑记 Intro 我们有一些服务是 dotnet framework 的,不能直接跑在 docker linux container 下面,最近一直在折腾把它部署 ...

  4. 使用 OAuth2-Server-php 在 Yii 框架上搭建 OAuth2 Server

    原文转自 http://www.cnblogs.com/ldms/p/4565547.html Yii 有很多 extension 可以使用,在查看了 Yii 官网上提供的与 OAuth 相关的扩展后 ...

  5. 配置SQL Server去使用 Windows的 Large-Page/Huge-Page allocations

    配置SQL Server去使用 Windows的 Large-Page/Huge-Page  allocations 目录表->页表->物理内存页 看这篇文章之前可以先看一下下面这篇文章 ...

  6. minikube windows hyperx填坑记

    minikube windows hyperx填坑记 安装了一天半,还是没行,先放弃 开始 minikube start --vm-driver=hyperv --hyperv-virtual-swi ...

  7. 使用 OAuth2-Server-php 搭建 OAuth2 Server

    Yii 有很多 extension 可以使用,在查看了 Yii 官网上提供的与 OAuth 相关的扩展后,发现了几个 OAuth2 的客户端扩展,但是并没有找到可以作为 OAuth2 Server 的 ...

  8. Installing OpenSSH from the Settings UI on Windows Server 2019 or Windows 10 1809

    Installing OpenSSH from the Settings UI on Windows Server 2019 or Windows 10 1809 OpenSSH client and ...

  9. CAS3.5.x(x>1)支持OAuth2 server

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...

随机推荐

  1. BZOJ 2726: [SDOI2012]任务安排 [斜率优化DP 二分 提前计算代价]

    2726: [SDOI2012]任务安排 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 868  Solved: 236[Submit][Status ...

  2. CF1060E Sergey and Subway 思维

    分两种情况讨论 一种为奇数长为$L$的路径,在经过变化后,我们需要走$\frac{L}{2} + 1$步 一种为偶数长为$L$的路径,在变化后,我们需要走$\frac{L}{2}$步 那么,我们只需要 ...

  3. [BZOJ3309]DZY Loves Math(莫比乌斯反演+线性筛)

    $\sum\limits_{T=1}^{n}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum\limits_{d|T}f(d)\mu(\fr ...

  4. 洛谷.4383.[八省联考2018]林克卡特树lct(树形DP 带权二分)

    题目链接 \(Description\) 给定一棵边带权的树.求删掉K条边.再连上K条权为0的边后,新树的最大直径. \(n,K\leq3\times10^5\). \(Solution\) 题目可以 ...

  5. 【单调队列】BZOJ1342-[Baltic2007]Sound静音问题

    [题目大意] 给出一个n个数的序列,以哪位位置为开头的长度为m的区间满足该区间的最大值与最小值的差≤一个定值. [思路] 单调队列……说一下单调队列比较方便的操作. 把第一个先丢进去,开始条件为hea ...

  6. C++ tuple类型

    前言 最近在看C++ Primer的时候,对于对象移动一直不太懂,所以在查找各种资料,仔细研究代码后,打算写篇博客记录下来,果然还是不要得过且过,看见不懂的就查,弄懂为止最好了. 对象移动 很多时候都 ...

  7. hdu 2251 Dungeon Master bfs

    Dungeon Master Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 17555   Accepted: 6835 D ...

  8. Linux学习之CentOS(十三)--CentOS6.4下Mysql数据库的安装与配置(转)

    原文地址:http://www.cnblogs.com/xiaoluo501395377/archive/2013/04/07/3003278.html 如果要在Linux上做j2ee开发,首先得搭建 ...

  9. Bootstrap 3之美03-独立行,文字环绕,图片自适应,隐藏元素

    本篇主要包括: ■  添加独立的一行■  文字环绕■  图片自适应■  隐藏元素 添加独立的一行 在id为body的section和id为main的section之间,添加2张图片. 我们发现,新加的 ...

  10. centos安装sqlserver

    centos安装sqlserver 必要條件 您必须具有 RHEL 7.3 或 7.4 计算机至少 2 GB的内存. 若要在自己的计算机上安装 Red Hat Enterprise Linux,请转到 ...