12.7.1. 接口安全问题
来源IP控制,即黑白名单,获取IP地址需要考虑X Forward for
IP计数器,单位时间内IP访问次数达到阀值,就提示稍后连接
用户名密码认证
动态验证码
证书加密
md5/sha1 数字摘要 校验
SSL / TSL 证书加密
12.7.2. 访问接口协议
机遇http的实现方式有下面几种。 http协议传统post/get 方式 soap 简单对象访问协议 xmlrpc 机遇xml的协议 json 近年来兴起的一种数据序列化传输方法 http无状态协议,不能保证连接100%有效性。http方式受限制与浏览器,对于并发控制,超时时间,通信数据长度都有严格的限制。 例如:一般浏览器运行超时时间都是30秒或60秒,当你通过http方式访问接口时,你的程序因运行超过30秒被浏览器强行中断;另外当你提交的数据超过浏览器限制长度时也会返回错误。 结局上述问题方法是将借口独立出一台服务器,单独设置超时时间等配制 http 方式有诸多缺陷,当仍被广泛使用,他的特点是容易开发,开发人员不需要额外学习,如post/get方式 http 方式的优势是它可以携带Cookie/Session TCP/UDP Socket 方式 TCP 这是唯能保证不间断时时传输手段,开发难度很高,目前web开发人员中能写出高效的多线程socket程序的人很少。 其中涉及很多知识,例如:进程,线程,锁,列队,进程间通信,共享内存,以及信号处理等等;没有10年功力很难写出安全,稳定,高效,可扩展的程序 UDP 能够发送大数据包
12.7.3. 接口性能问题
必须考虑接口最大会话数 处理请求后到返回数据所花费的时间 接口应该支持负载均衡,通过增加节点数量,快速扩展;同时添加与撤除节点不会影响接口的通信(包括节点硬件故障);同时接口应该具备健康状态检查功能。
第 23 章 Web Service Security
<?php /* * ===================================== * Website: http://netkiller.github.com * Author: neo <netkiller@msn.com> * Email: netkiller@msn.com * ===================================== */ class Logging { protected $file; public function __construct($logfile = "/tmp/debug.log"){ $this->file = fopen($logfile,"a+"); } public function __destruct() { //fclose($this->file); } public function close() { fclose($this->file); } private function write($msg){ fwrite($this->file,date('Y-m-d H:i:s').' '.$msg."\r\n"); } public function info($msg){ $this->write(__FUNCTION__.' '.$msg); } public function warning($msg){ $this->write(__FUNCTION__.' '.$msg); } public function error($msg){ $this->write(__FUNCTION__.' '.$msg); } public function debug($msg){ $this->write(__FUNCTION__.' '.$msg); } } class Permission{ protected $_PERMISSION = array(); public function __construct($login){ $test = array( 'neo' => array( 'News'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ), 'RSS'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ) ), 'jam' => array( 'News'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ), 'RSS'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ) ) ); //print_r($test); $this->load($test[$login]); } public function load($arr){ $this->_PERMISSION = $arr; } public function is_allowed($class, $fun){ $class = trim($class); $fun = trim($fun); //echo $class, $fun; //print_r($this->_PERMISSION); if(array_key_exists($class,$this->_PERMISSION)){ if(array_key_exists($fun,$this->_PERMISSION[$class])){ if($this->_PERMISSION[$class][$fun] == 'Y') return true; //return in_array("Y",$this->_PERMISSION[$class][$fun]); } } return false; } public function is_denied($class, $fun){ return (!$this->is_allowed($class, $fun)); } public function scan(){ return true; } } class News extends Permission{ private $logging; public function __construct(){ parent::__construct('neo'); $this->logging = new Logging('/tmp/news.log'); } public function __destruct() { $this->logging->debug('news->get permission denied!!!'); $this->logging->close(); } public function add(){ if(!$this->is_allowed(__CLASS__,__FUNCTION__)) return; print("Allowed!!! \r\n"); $this->logging->info('news->add ok'); } public function get(){ if( $this->is_denied(__CLASS__,__FUNCTION__)) { print("Denied!!! \r\n"); $this->logging->warning('news->get permission denied!!!'); } } } $news = new News(); $news->add(); $news->get();
权限来自下面数组数据,这里仅仅提供一个例子,管理权限你可以单独实现一个class,实现供权限管理功能,最终后转化为下面的数据结构即可。例如你可以将权限写入数据库,最终拼装如下数字让Permission顺利load即可。
array( 'neo' => array( 'News'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ), 'RSS'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ) ), 'jam' => array( 'News'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ), 'RSS'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ) ) );
public function is_allowed($class, $fun) 用户判断权限是否合法。
23.2. 演示
这里提供了一个 News 类,用于演示怎样控制每个function的权限。
同时还提供了一个简单的 Logging 类用于记录程序运行日志。
有了上面的例子就可以将News应用于SOAP一类Web Service上,用来控制每个方法的权限
23.3. 增加7 Layer防火墙
上面仅仅对于方法控制权限,接下来我们为程序增加7层防火墙功能
<?php /* * ===================================== * Website: http://netkiller.github.com * Author: neo <netkiller@msn.com> * Email: netkiller@msn.com * ===================================== */ class Firewall{ protected $status; protected $policy; protected $chain; protected $rule; protected $match; private $debug; //$get,$post,$cookie,$server; public function __construct() { $this->name = "Firewall"; } public function __destruct() { //print "Destroying " . $this->name . "\n"; } public function enable(){ $this->status = true; } public function disable(){ $this->status = false; } public function get(){ if($this->status){ $this->chain = $_GET; return($this); }else{ return($this->status); } } public function post(){ if($this->status){ $this->chain = $_GET; return($this); }else{ return($this->status); } $this->chain = $_POST; } public function cookie() { if($this->status){ $this->chain = $_COOKIE; return($this); }else{ return($this->status); } } public function server(){ if($this->status){ $this->chain = $_SERVER; return($this); }else{ return($this->status); } } public function match($key, $value){ if($this->debug) print_r($this->chain); $this->match = false; if(!array_key_exists($this->chain, $key)){ if($this->chain[$key] == $value){ $this->match = true; } } return($this); } public function policy($p){ $this->policy = $p; } public function counter($tm, $cnt){ return($this); } public function allow($fun = null){ if($this->status && $this->match){ if($fun){ $fun(); } } $this->destroy(); return($this->status); } public function deny($fun = null){ if($this->status && $this->match){ if($fun){ $fun(); } } $this->destroy(); return($this->status); } public function debug($tmp){ $this->debug = $tmp; } public function ip($ipaddr){ return $this->server()->match('REMOTE_ADDR', $ipaddr); } public function destroy(){ $this->chain = array(); $this->match = false; } }; #include_once('firewall.php') $fw = new Firewall(); $fw->debug(true); $fw->debug(false); $fw->enable(); //$fw->disable(); function test(){ echo 'OK'; }; function allow(){ echo 'allow'; }; function deny(){ echo 'deny'; }; //$fw->policy('blacklist'); $fw->ip('192.168.3.17')->allow('allow'); $fw->ip('192.168.3.17')->deny('deny'); $fw->counter('1m',5)->match('id','1000')->deny('test'); /* $fw->ip('172.16.0.0/24')->allow(); $fw->ip('172.16.0.0','255.255.255.0')->allow(); $fw->header(array('User-Agent' => 'MSIE5'))->deny() */ $fw->get()->match('id','1000')->deny('test'); $fw->get()->match('name','chen')->allow('test'); //$fw->get()->match(array('id' => '1000'))->deny(); /* $fw->post()->data(array('action'=>'/login.php'))->allow() $fw->cookie()->data(array('userid'=>'test'))->deny() */ $fw->server()->match('HTTP_REFERER', 'http://www.mydomain.com/index.html')->allow('test'); $fw->server()->match('REQUEST_METHOD', 'GET')->deny('test'); $fw->disable(); //$fw->destroy();
这里仅仅给你一个思路,我并没有写完程序。例如控制IP请求次数可以如下实现,请自行改善程序
<?php /* * ===================================== * Website: http://netkiller.github.com * Author: neo <netkiller@msn.com> * Email: netkiller@msn.com * ===================================== */ require 'SharedConfigurations.php'; $single_server = array( 'host' => '127.0.0.1', 'port' => 6379, 'database' => 0 ); $multiple_servers = array( array( 'host' => '127.0.0.1', 'port' => 6379, 'database' => 15, 'alias' => 'first', ), array( 'host' => '127.0.0.1', 'port' => 6380, 'database' => 15, 'alias' => 'second', ), ); $client = new Predis\Client($single_server, array('prefix' => 'fw:')); $key=$_SERVER['REMOTE_ADDR']; if(!$client->exists($key)){ $client->setex($key, 20, 1); }else{ $client->incrby($key,1); } $counter = $client->get($key); if($counter > 10){ echo 'Deny'; } print_r($client->get($key)); //var_dump($client->keys('*'));