项目要开工了,服务器先行,下面讲讲我设计的一个客户端与服务器通信的认证系统。
背景
根据对新浪微博,人人网,云端书库,摩根书院等移动客户端的通信技术分析,得到了一些重要技术数据。
对oauth的使用
这些客户端都是官方客户端,对客户端都没有采用oauth的认证,其实想想也是没必要的,oauth只是用于第三方在不需要用户的用户名和密码的情况下获得用户的授权并操作受保护的资源。
用户的认证和penapi的保护
用户认证包括注册和登录,为了防止用户名和密码被不良青年窃听,看一下上述四个软件分别是怎么做的。
新浪微博
我原本以为新浪微博对用户信息的保护会非常到位,但是以抓包分析,结果让我大吃一惊。新浪用了http的basic认证协议,只是简单的把用户名和密码进行base64加密,更过分的是http头中还携带了明文的用户名和密码,这里所有的加密都是浮云了,新浪直接裸奔。意味着我只要在图书馆一坐,开启网卡的无线抓包模式,我就能获取到所有在上新浪微博的用户的用户名和密码,这太糟糕了。
云端书库
云端书库是闭门造车,一切认证在他面前都是浮云。当我在测他的搜索功能时,改软件竟然直接发出了资源获取的get请求,并且是直接定位到资源的url。唯一的可能就是改软件已经把整个数据库放在了客户端,所有搜索在客户端直接搞定。这样他就不需要公布openapi,这些已经都是浮云了。这样一想,他的客户端安装包有100M多也就想得通了。
摩根书院
他的openapi没什么保护,直接用浏览器也能进行调用。
人人网
这里人人网的技术含量还是算最高的,在用户注册登录上,所有的数据包都不会出现密码,不管是明文还是加过密的,只会出现一个apikey,niquleid 和uid,所有的认证和api调用都解决了。关键是注册时,人人网选择了短信形式注册,避免了密码泄露的危险。
跟据这几个客户端的分析,考虑到以后我们可能会有付费会员,用户的认证和openapi的保护是非常重要的。下面是设计的系统方案。
约定
下面会用到一些变量,这里进行解释。
key, 提前约定的客户端与服务器之间的密钥,用于保证apikey申请系统不被攻击,只有我们的客户端可以申请,确定每一个申请的apikey都是有效的。
apikey用于对系统openapi的保护,每个客户端对应一个apikey,因此可以对用户的一些操作进行限制,比如一个小时内某些方法或资源调用的次数。
token用户保护用户的密码,避免用户的密码在数据包中出现。特别针对以后有可能出现的收费用户,密码的保护非常重要。
系统方案
apikey注册
app第一次运行连接网络后获取apikey,以后一直保存该key。步骤伪代码如下:
客户端:
- $key = '提前定义好的密钥字符串'; // 客户端和服务器端都有,其他人都不知道这个密钥字符串。
- do{
- $once = func() ; //func() 会随机产生16位字符串。
- $auth = md5($key:$once);
- $result = request('GET', $once, $auth); //发送get请求道服务器,进行认证并获取apikey。
- }while($result->state != 0)
- $apikey = $result->key;
服务器:
- $key='提前定义好的密钥字符串';
- $request = $SERVER[GET]; //获取客户端的请求信息
- $once=$request[once];
- $serverauth = md5($key,$once);
- if($serverauth == $request[auth] && !$db->search($once)) //认证串相等并且数据库中没有这个once
- {
- $apikey = keyfunc(); //产生apikey
- response($state=1, $apikey); //返回给客户端状态0(成功)和apikey.
- $db->store($apikey, $once); //存储apikey
- }
- else if($serverauth == $request[auth] && $db->search($once)) //认证串相等,但数据库中已经有这个once,证明是攻击者,窃取了别人的认证信息。
- {
- response($state=1); //封杀这个IP的请求;
- }
- else
- response($state=1); //可能也是攻击者,也可能是客户端发出的请求发生了什么未知问题。
用户注册
请求中携带 apikey, username, password的md5值,email。
客户端
- $passwordmd5($password);
- $result = $request('POST', $apikey, $username, $passwordmd5, $email );
- if($result == 0)
- {return; //注册成功}
- else
- return; //用户名已经被注册
服务器
- $request = $SERVER[POST];
- if(!($db->search($request[apikey])))
- { response($state = 1); return; } //apikey 错误, 可能是不坏好意的人恶意调用这个openapi。
- if($db->search($request[username]))
- { response($state = 2); return;} //用户名已经被注册了
- $db->store($request[username], $request[passwordmd5], $email);
- response($state = 0); //注册成功
- return;
用户登陆
- $token = md5($apikey, md5($password));
- $result = request('POST', $username, $apikey, $token);
- if($result->state = 0) //登陆成功
- {
- $db->store($token); //允许访问用户资源的令牌
- }
- else
- {
- //用户名或密码错误;
- }
服务器
- $request = $SERVER[POST];
- if(!($db->search($request[apikey])))
- { response($state = 1); return; } //apikey 错误, 可能是不坏好意的人恶意调用这个api。
- if(!($passwordmd5 = $db->search($request[username])))
- { response($state = 2); return;} //找不到对应用户名的密码,服务器存的都是密码的MD5值
- $servertoken = md5($request[apikey], $passwordmd5);
- if($servertoken != $request[$token])
- { response($state = 1); return; } //某些未知错误,一般不可能发生
- response($state = 0);
- $db->store($token); //存储经过验证的$token
总结
经过这样的认证,以后用户操作自己的资源时只要带上 $username,$apikey, $token 这三个验证信息就行了。
总得来说这个系统能
- 保护了用户的密码不在网络上明文传输,唯一出现的一次机会是注册时传输的密码的MD5值。
-
对于openapi,有了apikey之后就能对客户端调用openapi做一个有规则的限制,避免了不良IT青年恶意频繁调用openapi对服务器造成压力。
-
Apikey的申请规则又保护了申请apikey的openapi,只对我们的客户端开放,其他恶意IT青年都不能无法申请,因为他们不知道我们的约定key。