接口地址加密 和 API权限设计 保护服务器上的数据安全通信传输

分类:性能优化_架构设计| 发布:佚名| 查看: | 发表时间:2015/2/28

移动应用中,通过在客户端对访问的url进行加密处理来保护服务器上的数据

我认为,保护服务器端的数据,有这么几个关键点:

1、不能对使用体验产生影响,这就排除掉了诸如每次接口调用都要求用户输入验证码这样的做法
2、接口调用的网络交互需要无规律可循,比如article/1 –> article/1000 这样的接口就太容易被其他人爬走了
3、要严格意义上阻击爬虫,需要每一次网络请求都是不可重放的,这样才能避免其他人通过监听网络交互并重放来爬取数据
4、对服务器端编码不产生太大影响,如果要对服务器端伤筋动骨的大改,肯定是要不得的
通常,我们会采用一种简单有效的方法:对服务器返回的数据加密来解决,但是,这种做法并没有解决上面所提到的第二点,接口调用的时候url的规律性太强,网络监听一下数据,就很容易找到url地址的规律了,加密的破解也很简单,反编译直接定位到解密函数,拿到密钥。当然,在强大的反编译工程面前,一切努力都是徒劳的,不管你用何种方法,都是可以把中间的逻辑找到并模拟成一个客户端来爬数据的。

我下面就提出一个破解更加复杂一些的方法,在客户端产生请求时,对接口url进行RSA加密处理。

假设我们本来需要访问 http://api.camnpr.com/articles 这样的一个接口,接口返回json数据。在客户端访问之前,我们先对这个url进行这样的处理:

加客户端时间戳:http://api.camnpr.com/1322470148/articles
对url的path段进行rsa加密,然后base64:http://api.camnpr.com/TBhIskCgCN+WMK3PftbYzPQFAKvx9sE9OMOxvL00kCBlNiKw2C1Mb7oGcfUepTxauG06NLBNhr5BFtjt7Xu7uwdpUYyVcFRdI37SVyGRCOzaxACOGXGpX5dHZqQJia0icxwWJ+D1RiJqxFWQ++3/IgUOgDzgvQnPIl420bpztB8=
我们真实访问的地址就变成了这样一个长长的 url 结构,我们通过rsa算法的padding参数和时间戳,就可以让这个后面长长的bas64串在每次访问的时候都发生变化,同时,我们可以在服务器端把一个小时之内的请求过的串都记下来,并不让再次访问,这样就防止了爬虫的重放请求尝试。

在服务器端,我们就需要在做响应之前,把url还原回来。在服务器端,现在都是框架的天下,一般都有唯一的入口,如果使用的是php语言,主要在入口的index.php加上一些代码就可以了:

if ($_SERVER['HTTP_HOST'] == "api.camnpr.com"){ // 只针对api这个域名做处理
    include_once dirname(__FILE__).'/protected/components/EncryptUtil.php'; // 加解密库,你需要实现你自己的加解密类
    $request_uri = $_SERVER['REQUEST_URI'];
    if(isset($_SERVER['HTTP_HOST'])){
        if(strpos($request_uri,$_SERVER['HTTP_HOST'])!==false){
            // 把 REQUEST_URI 中可能包含的host信息去除掉
            $request_uri=preg_replace('/^\w+:\/\/[^\/]+/','',$request_uri);
        }
    }
    $encoded = base64_decode(substr($request_uri, 1));
    if($encoded && strlen($encoded) % 128 ===0){
        $real_uri = EncryptUtil::private_decrypt($encoded);         // 解密url路径
        if(!$real_uri){ echo ":)"; return; }                        // 解密失败
        if(preg_match("/([0-9]+)\\/(.+)/", $real_uri, $matches)){   // 提取出时间戳和真实的url请求地址
            $timestamp = $matches[1];                               // 客户端请求的时间戳
            $real_uri = $matches[2];                                // 客户端请求的真实地址
            $_SERVER['REQUEST_URI'] = $real_uri;                    // 置上本来应该有的全局$_SERVER['REQUEST_URI']
            if(preg_match("/^[^?]+\\?(.+)/", $real_uri, $matches)){
                $_SERVER['QUERY_STRING'] = $matches[1];             // 置上本来应该有的全局$_SERVER['QUERY_STRING']
                parse_str($_SERVER['QUERY_STRING'], $array);
                $_REQUEST = array_merge($_REQUEST, $array);         // 置上本来应该被设置的全局$_REQUEST
                $_GET = array_merge($_GET, $array);                 // 置上本来应该被设置的全局$_GET
            }
        }else{ // url的格式不符合,没有包含时间戳
            echo ":)"; return;
        }
    }else{ // url的长度不符合规则
        echo ":)"; return;
    }
}


在经过这样一段代码处理之后,框架就一切正常,其他代码都不需要做变更,就有了rsa加密的url支持,当然,这几行代码还是不能阻止重放攻击的,里面并没有对请求过的url进行记录处理,要实现url访问的唯一性,还需要额外的更多代码。

服务器端完成了,那客户端也同样需要做相应操作,我这里就不详细讲解了,贴上一段修改过的实际运行的代码,IOS,应用了 three20库,并兼容TTURLRequest缓存机制。


Android的Java版本我就把实际运行中的代码的http部分抽离出来,因为牵涉到一些相关配置,代码不能正常编译,不过也放在这里,以供参考。

android-rsa-http.zip下载地址

用法示例:

BaiyiApiRequest request = new BaiyiApiRequest("articles/1");
request.setListener(this);
request.start();

API权限设计总结:

最近在做API的权限设计这一块,做一次权限设计的总结。

1. 假设我们需要访问的API接口是这样的:http://xxxx.com/openapi/v1/get/user/?key=xxxxx&sign=sadasdas&timestamp=2013-03-05 10:14:00&c=c&a=a&d=d

 

2. 接口调用的控制器:openapi/v1/get/user/

 

3. 步骤一:作为服务端,首先要检查参数是否正确:key (用户的key) ;sign(加密的签名串) ;timestamp (请求的时间,服务端对请求有时间生效),这些参数如果有一个参数没传递,肯定返回参数不正确的结果。

 

4. 步骤二:参数如果都传递正确,这个时候需要检查API的白名单权限,API也就是(openapi/v1/get/user/)是否存在在我们的数据库中,一般会有一张API的数据表,如果调用的API不在我们的数据库白名单中或者这个API已经关闭访问了,那么要返回禁止访问的结果。

 

5. 步骤三: 如果API在白名单中,那么现在就要检查用户的KEY是否正确了,服务端会有一张用户权限表,这个数据表主要用来记录用户的key secret(密钥) 以及API权限列表,检查这个用户对访问的API(openapi/v1/get/user/)是否有权限,如果有权限则通过,没权限则关闭。

 

6. 步骤四: 如果用户权限通过,这个时候就到了最重要的一步,SIGN签名的验证。

签名算法:

加密方式 md5(POST参数(升序排序,除key sign参数除外) + 用户密钥) 

PHP加密算法代码:

foreach ($p as $v) {  
            $temp = explode("=", $v);  
            $pArr[$temp[0]] = $temp[1];  
        }  
ksort($pArr);  
        foreach ($pArr as $k => $v) {      
            $pStr2 .= $k . $v ;  
        }  
md5($pStr2 . $secret) 

注意:加密的时候,需要将timestamp带上,防止客户端篡改。

客户端,将自己需要传递的参数进行升序排序,然后加上自己key对应的密钥(密钥在服务端数据库中有一份保存,这个是不能对外公开的)进行MD5加密,通过参数sign传递到服务端。

服务端拿到sign值后,对传递过来的参数也进行同样的算法排序,并经过用户的key查询得到密钥,然后进行一次加密算法,得到的服务端的sign和客户端传递过来的sign进行比较,如果相同则表示是可以通过的,如果中途有人篡改数据等,那么最终加密出来的sign就是不一致的,这样保证了用户传递数据的可靠性和安全性。

 

7. 步骤五:检查时间戳时间,比较客户端时间和服务端时间是否在10分钟之内,如果10分钟之外了,那么返回超时的提示,这样能保证调用过的接口数据能在一定时间内销毁掉。

 

8. 步骤六:调用相应逻辑

365据说看到好文章不转的人,服务器容易宕机
原创文章如转载,请注明:转载自郑州网建-前端开发 http://camnpr.com/
本文链接:http://camnpr.com/performance/1937.html