SAASAPIServer.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. require_once __DIR__.'/SAASCryptAES.php';
  3. date_default_timezone_set('Asia/Shanghai');
  4. class SAASAPIServer
  5. {
  6. /**
  7. * 应用开发的AppID
  8. */
  9. protected $appid;
  10. /**
  11. * 应用开发的App密钥(用于API服务接口调用时的参数签名)
  12. */
  13. protected $appsecret;
  14. /**
  15. * 构造函数
  16. *
  17. * @param $appid 应用开发的AppID
  18. * @param $appsecret 可选参数,应用开发的App密钥(用于API服务接口调用时的参数签名),若为空,则通过$appid从数据库中获取$appsecret
  19. * @return void
  20. */
  21. public function __construct($appid=null, $appsecret=null)
  22. {
  23. if (empty($appid) && empty($appsecret)) {
  24. $this->loadDefaultAppIdAndSecret();
  25. } else {
  26. $this->appid = $appid;
  27. $this->appsecret = $appsecret;
  28. }
  29. }
  30. /**
  31. * 从HTTP请求参数中获取远程调用者上传的最新授权信息内容,并以此更新本地授权目录下的各授权域名授权文件。
  32. *
  33. * @param $licenseRootDir 本地授权文件根目录路径
  34. * @param $requests HTTP请求参数数组,如果为空时,直接从$_REQUEST变量中获取请求参数,请求参数包含内容:
  35. * 1) domains 授权域名列表,json格式字符串,如:["u1.o2o.fanwe.com","*.yydb.fanwe.com"]
  36. * 2) license 授权文件内容
  37. * @return 更新结果,数组对象,如:array("errcode"=>0,"errmsg"=>"")。
  38. */
  39. public function updateLocalLicense($licenseRootDir, $requests=null)
  40. {
  41. // 初始化相关参数
  42. if (empty($licenseRootDir)) {
  43. $licenseRootDir = __DIR__.'/licenses/';
  44. }
  45. if (empty($requests)) {
  46. $requests = $_REQUEST;
  47. }
  48. // 验证请求参数签名验证
  49. $ret = $this->verifyRequestParameters($requests);
  50. if ($ret['errcode'] != 0) {
  51. return $ret;
  52. }
  53. // 授权域名和授权文件内容参数获取和验证
  54. $domains = array_key_exists('domains', $requests) ? trim($requests['domains']) : '';
  55. $content = array_key_exists('license', $requests) ? trim($requests['license']) : '';
  56. if (empty($domains)) {
  57. return array('errcode'=>1002,'errmsg'=>'Invalid "domains" argument!');
  58. }
  59. if (empty($content)) {
  60. return array('errcode'=>1002,'errmsg'=>'Invalid "license" argument!');
  61. }
  62. $domainsArr = json_decode($domains, true);
  63. if (is_null($domainsArr)) {
  64. return array('errcode'=>1002,'errmsg'=>'Invalid "domains" argument!');
  65. }
  66. // 更新本地授权
  67. $failDomains = array();
  68. foreach($domainsArr as $domain) {
  69. // 域名有效性判断
  70. $fixedDomain = trim($domain);
  71. if (empty($fixedDomain)) continue;
  72. // 替换掉域名中的星号
  73. if (strpos($fixedDomain, '*.') === 0) { // start with *.
  74. $fixedDomain = str_replace('*', '^', $fixedDomain); // 替换掉星号
  75. }
  76. // 创建保存授权文件的目录
  77. $licenseDir = $licenseRootDir.'/'.$fixedDomain;
  78. if (!file_exists($licenseDir)) {
  79. mkdir($licenseDir, 0777, true);
  80. }
  81. // 保存授权文件
  82. $saveRet = true;
  83. $tryCount = 0;
  84. do {
  85. $saveRet = file_put_contents($licenseDir.'/license', $content);
  86. } while($saveRet === false && $tryCount++ < 3);
  87. if ($saveRet === false) {
  88. $failDomains[] = $domain;
  89. }
  90. }
  91. // 输出结果
  92. if (!empty($failDomains)) {
  93. return array('errcode'=>3001,'errmsg'=>'Save license file error!','data'=>$failDomains);
  94. } else {
  95. return array('errcode'=>0,'errmsg'=>'');
  96. }
  97. }
  98. /**
  99. * 验证SAAS系统API请求参数是否有效,这些请求参数必须按照SAAS系统API服务接口参数和签名规范进行传递。这些参数通过$_REQUEST变量获取。
  100. *
  101. *
  102. * @param $params HTTP请求参数数组,如果为空时,直接从$_REQUEST变量中获取请求参数
  103. * @return 验证结果,数组对象,如:array("errcode"=>0,"errmsg"=>""),验证通过时,errcode为0。
  104. */
  105. public function verifyRequestParameters($params=null)
  106. {
  107. // 获取请求参数
  108. if (empty($params)) {
  109. $params = $_REQUEST;
  110. }
  111. // 获取HTTP请求参数并进行有效性判断
  112. $appid = array_key_exists('appid', $params) ? trim($params['appid']) : '';
  113. $timestamp = array_key_exists('timestamp', $params) ? trim($params['timestamp']) : '';
  114. $signature = array_key_exists('signature', $params) ? trim($params['signature']) : '';
  115. if (empty($appid) || empty($timestamp) || empty($signature) || !is_numeric($timestamp)) {
  116. return array('errcode'=>1002,'errmsg'=>'Invalid argument!');
  117. }
  118. // 验证appid
  119. if ($appid != $this->appid) {
  120. return array('errcode'=>1005,'errmsg'=>'Invalid appid!');
  121. }
  122. // 计算参数签名
  123. $signParams = array();
  124. foreach($params as $key=>$value) {
  125. if ($key == 'signature') continue;
  126. $signParams[] = $key.$value;
  127. }
  128. sort($signParams, SORT_STRING);
  129. $signParamsStr = implode($signParams);
  130. $calcSignature = md5($this->appsecret.$signParamsStr.$this->appsecret);
  131. // 验证参数签名
  132. if (strtolower($signature) != strtolower($calcSignature)) {
  133. return array('errcode'=>1004,'errmsg'=>'Invalid signature!');
  134. }
  135. // 验证时间戳
  136. $timestamp = intval($timestamp);
  137. if (abs($timestamp - time()) > 600) { // 误差在10分钟之内
  138. return array('errcode'=>1003,'errmsg'=>'Invalid timestamp!');
  139. }
  140. // 一切顺利,返回成功结果
  141. return array('errcode'=>0,'errmsg'=>'');
  142. }
  143. /**
  144. * 将数组格式的结果数据转换成指定输出格式的字符串(目前只支持json格式字符串)
  145. * @param $result 数组格式结果数据
  146. * @return 指定输出格式的字符串(目前为json格式字符串)
  147. */
  148. public function toResponse($result)
  149. {
  150. return json_encode($result);
  151. }
  152. /**
  153. * 从方维系统间传递的安全地址中提取所传递的安全参数,并返回安全参数数组。如果参数已超时,那么返回false
  154. * @param $url 方维系统间传递的安全地址(带有加密后的安全参数),也可直接为请求参数字符串
  155. * @return 提取到的安全参数数组,参数超时时返回false
  156. */
  157. public function takeSecurityParams($url)
  158. {
  159. // 从$url中获取加密后的参数
  160. $paramName = '_saas_params';
  161. $pos = strpos($url, '?'.$paramName.'=');
  162. $start = 0;
  163. if ($pos === false) {
  164. $pos = strpos($url, '&'.$paramName.'=');
  165. }
  166. if ($pos === false) { // 未找到安全参数
  167. if (strpos($url, $paramName.'=') == 0) { // 判断地址只包含参数,首个参数就是安全参数,且没带问号和&号
  168. $pos = 0;
  169. $start = $pos + strlen($paramName) + 1;
  170. } else {
  171. return array();
  172. }
  173. } else {
  174. $start = $pos + strlen($paramName) + 2;
  175. }
  176. $end = strpos($url, '&', $start);
  177. $encstr = ($end === false) ? substr($url, $start) : substr($url, $start, $end - $start);
  178. $encstr = urldecode($encstr);
  179. // 对加密的参数进行解密并返回
  180. return $this->decodeSecurityParams($encstr);
  181. }
  182. /**
  183. * 解密方维系统间传递的安全地址中的加密参数。如果参数已超时,那么返回false
  184. * @param $paramsStr 加密参数字符串
  185. * @return 解密后得到的原始安全参数数组,参数超时时返回false
  186. */
  187. public function decodeSecurityParams($paramsStr)
  188. {
  189. // 对加密的参数进行解密
  190. $aes = new SAASCryptAES();
  191. $aes->set_key($this->appsecret);
  192. $aes->require_pkcs5();
  193. $decstr = $aes->decrypt($paramsStr);
  194. // 将解密后的json字符串转成数组
  195. $ret = empty($decstr) ? array() : json_decode($decstr, true);
  196. // 验证参数是否过期
  197. $timestamp = array_key_exists('_saas_timestamp', $ret) ? $ret['_saas_timestamp'] : 0;
  198. $timeout = array_key_exists('_saas_timeout', $ret) ? $ret['_saas_timeout'] : 0;
  199. if ($timestamp <= 0) {
  200. return false;
  201. }
  202. if ($timeout > 0) {
  203. if (abs($timestamp - time()) > $timeout * 60) { // 误差超过指定时间,已超时
  204. return false;
  205. }
  206. }
  207. // 删除数组中的时间戳和超时设置参数
  208. if (array_key_exists('_saas_timestamp', $ret)) {
  209. unset($ret['_saas_timestamp']);
  210. }
  211. if (array_key_exists('_saas_timeout', $ret)) {
  212. unset($ret['_saas_timeout']);
  213. }
  214. // 返回结果
  215. return $ret;
  216. }
  217. /**
  218. * 从saasapi.key配置文件中加载默认的appid和appsecret配置
  219. */
  220. private function loadDefaultAppIdAndSecret()
  221. {
  222. $keyfile = __DIR__.'/saasapi.key';
  223. if (!file_exists($keyfile)) {
  224. return;
  225. }
  226. $keystr = file_get_contents($keyfile);
  227. $keyinfo = json_decode($keystr, true);
  228. $this->appid = array_key_exists('appid', $keyinfo) ? $keyinfo['appid'] : '';
  229. $this->appsecret = array_key_exists('appsecret', $keyinfo) ? $keyinfo['appsecret'] : '';
  230. }
  231. }