1.22.1 过滤器服务

正如我们核心思想DI里面所说的,我们把后台很多功能资源都称为服务,所以在PhalApi框架中我们已经系统规定了DI()->filter为过滤器服务,以实现接口请求时的一些拦截操作,一如现在要说明的签名验证。

在接口进行初始化时,会自动调用已注册的过滤器服务 DI()->filter ,关键的代码如下:

  1. //$vim ./PhalApi/PhalApi/Api.php
  2. public function init()
  3. {
  4. $this->createMemberValue();
  5. $this->filterCheck();
  6. $this->checkStatus();
  7. }
  8. protected function filterCheck()
  9. {
  10. $filter = DI()->filter;
  11. if (isset($filter)) {
  12. $filter->check();
  13. }
  14. }

默认直接可用的接口验证

基于很多同学对接口签名验证比较陌生,从框架推出签名验证以来,很长一段时间内,很多同学对于如何实现一个接口签名依然不知如何下手。为了给大家更好的便利性,我们提供了一个基本版的接口验证服务。

主要是基于md5进行的签名生成,这个只能作为一般性的参考。大家可以在此基础上进行调整延伸。

默认情况下,可以去掉注释开启使用PhalApi_Filter_SimpleMD5进行接口验证,即:

  1. //签名验证服务
  2. DI()->filter = 'PhalApi_Filter_SimpleMD5';

其验签的算法如下(如注释所示):

  1. 1、排除签名参数(默认是sign
  2. 2、将剩下的全部参数,按参数名字进行字典排序
  3. 3、将排序好的参数,全部用字符串拼接起来
  4. 4、进行md5运算

以下面的示例参数为例,即:

  1. 1、排除签名参数(默认是sign
  2. ?service=Default.Index&username=dogstar
  3. 2、将剩下的全部参数,按参数名字进行字典排序
  4. service=Default.Index
  5. username=dogstar
  6. 3、将排序好的参数,全部用字符串拼接起来
  7. "Default.Indexdogstar" = "Default.Index" + "dogstar"
  8. 4、进行md5运算
  9. sign = 35321cc43cfc1e4008bf6f1bf9b7e3b8 = md5("Default.Indexdogstar")
  10. 5、请求时,加上签名参数
  11. ?service=Default.Index&username=dogstar&sign=35321cc43cfc1e4008bf6f1bf9b7e3b8

下面是两个调用示例,错误请求下(即签名失败):

  1. http://localhost/phalapi/Public/demo/?service=Default.Index&username=dogstar
  2. 返回:
  3. {
  4. "ret": 406,
  5. "data": [],
  6. "msg": "非法请求:签名错误"
  7. }
温馨提示:签名错误情况下,可以查看日记获得正确的sign,如:
  1. 2015-10-23 23:16:16|DEBUG|Wrong Sign|{"needSign":"35321cc43cfc1e4008bf6f1bf9b7e3b8"}

正常请求下(带sign签名):

  1. http://localhost/phalapi/public/demo/?service=Default.Index&username=dogstar&sign=35321cc43cfc1e4008bf6f1bf9b7e3b8

如果不想使用sign作为关键的签名参数,可以在注册时指定,如使用缩写s:

  1. DI()->filter = new PhalApi_Filter_SimpleMD5('s');

1.22.2 微信签名示例

所以,如果我们需要实现签名验证,只需要简单的两步即可:

  • 1、实现过滤器接口 PhalApi_Filter::check()
  • 2、注册过滤器服务 DI()->filter
    下面以大家熟悉的 微信验签 为例,进行示例说明。

(1)实现过滤器接口 PhalApi_Filter::check()

通常我们约定返回ret = 402表示验证失败,所以当签名失败时,我们可以返回ret = 402以告知客户端签名不对。根据微信的检验signature的PHP示例代码,我们可以快速实现自定义签名规则,如:

  1. //$ vim ./Demo/Common/SignFilter.php
  2. <?php
  3. class Common_SignFilter implements PhalApi_Filter
  4. {
  5. public function check()
  6. {
  7. $signature = DI()->request->get('signature');
  8. $timestamp = DI()->request->get('timestamp');
  9. $nonce = DI()->request->get('nonce');
  10. $token = 'Your Token Here ...';
  11. $tmpArr = array($token, $timestamp, $nonce);
  12. sort($tmpArr, SORT_STRING);
  13. $tmpStr = implode( $tmpArr );
  14. $tmpStr = sha1( $tmpStr );
  15. if ($tmpStr != $signature) {
  16. throw new PhalApi_Exception_BadRequest('wrong sign', 1);
  17. }
  18. }
  19. }

(2)注册过滤器服务 DI()->filter

随后,我们只需要再简单地注册一下过滤器服务即可,在init.php初始化文件最后追加:

  1. //$ vim ./Public/init.php
  2. //签名验证服务
  3. DI()->filter = 'Common_SignFilter';

(3)运行效果

当我们再次请求接口时,如默认的服务:/demo/?service=Default.Index,即会出现以下的错误:apic

即:

  1. {
  2. "ret": 401,
  3. "data": [
  4. ],
  5. "msg": "非法请求:wrong sign"
  6. }

如果符合接口签名的验证,则会正常返回我们熟悉的内容,如:

  1. /demo/?service=Default.Index&signature=b75e0a1b574d4e111a1d6ed3c9cfbe2ccdc09404&timestamp=123&nonce=123

会返回:

  1. {
  2. "ret": 200,
  3. "data": {
  4. "title": "Default Api",
  5. "content": "PHPer您好,欢迎使用PhalApi!",
  6. "version": "1.1.0",
  7. "time": 1423055188
  8. },
  9. "msg": ""
  10. }

1.22.3 特殊的场景

(1)个别接口不需要验签?

在注册好统一的接口验签的过滤器拦截服务后,是会存在这样一种情况:即个别的接口不需要签名。

而这种情况,我们也是有考虑到的。所以在提供了公共的功能的情况下,我们是可以快速灵活地进行定制化和扩展。

当我们个别的接口不需要签名验证时,只需要简单地在接口子类里面重定义过滤器的检测即可,如在我们熟悉的默认服务器取消签名验证:

  1. //vim ./Demo/Api/Default.php
  2. class Api_Default extends PhalApi_Api
  3. {
  4. //....
  5. protected function filterCheck()
  6. {
  7. }
  8. }

1.22.4 接口服务白名单配置

除了可以像上面这样编码实现,排除个别接口服务的接口验证,还可以使用接口服务白名单配置,通过框架自身实现对指定配置的接口服务排除。即调用的接口服务,如果配置了白名单,则不调用过滤器。

接口服务白名单配置是:app.service_whitelist,即配置文件./Config/app.php里面的service_whitelist配置,其默认值是:

  1. 'service_whitelist' => array(
  2. 'Default.Index',
  3. ),

如源代码里的注释所示,配置的格式有以下四种。

类型 配置格式 匹配规则 示例及说明
全部 . 匹配全部接口服务(慎用!) 如果配置了此规则,即全部的接口服务都不触发过滤器。
方法通配 Default.* 匹配某个类的任何方法 即Api_Default接口类的全部方法
类通配 *.Index 匹配全部接口类的某个方法 即全部接口类的Index方法
具体匹配 Default.Index 匹配指定某个接口服务 即Api_Default::Index()

如果有多个生效的规则,按短路判断原则,即有任何一个白名单规则匹配后就跳过验证,不触发过滤器。

以下是更多的示例:

  1. 'service_whitelist' => array(
  2. '*.Index', // 全部的Index方法
  3. 'Test.*', // Api_Test的全部方法
  4. 'User.GetBaseInfo', // Api_User::GetBaseInfo()方法
  5. ),

配置好上面的白名单后,以下这些接口服务全部不会触发过滤器:

  1. // 全部的Index方法
  2. ?service=Default.Index
  3. ?service=User.Index
  4. // Api_Test的全部方法
  5. ?service=Test.DoSth
  6. ?service=Test.Hello
  7. ?service=Test.GOGOGO
  8. // Api_User::GetBaseInfo()方法
  9. ?service=User.GetBaseInfo
温馨揭示:以上白名单配置,需要PhalApi 1.4.0及以上版本,方可支持。

1.22.5 更好地建议

通常关于接口签名这块,我们还需要:

  • 1、为不同的接入方定义不同的密钥和私钥;

  • 2、如果业务需要,为各个接口、各个接入方分配调用权限;

  • 3、统一签名参数的规则,可以配置在./Config/app.php中的,如上面的签名需要的参数,我们可以追加统一的参数规则:

    1. /**
    2. * 应用接口层的统一参数
    3. */
    4. 'apiCommonRules' => array(
    5. 'signature' => array('name' => 'signature', 'require' => true),
    6. 'timestamp' => array('name' => 'timestamp', 'require' => true),
    7. 'nonce' => array('name' => 'nonce', 'require' => true),
    8. ),

原文: https://www.phalapi.net/wikis/1-22.html