App 项目在是否依赖后端服务方面,可以划分为两类:
一类是独立的客户端(Native App),无服务端;
另外一类是客户端和服务端(Web Server)结合,客户端的部分功能,需要 Web 后端提供服务。
接下来将主要讨论:
客户端和服务端结合的这类应用,
基于 HTTP 协议进行数据传输,在数据安全方面的处理手段。
首先,进行安全的数据通信,目的是为了对客户端请求进行:安全的身份认证, 防止数据泄密,防止数据被篡改。 为了达到这个目的,下面将分类讨论具体的措施。
由于 HTTP 请求是无状态的,而且客户端 App 没有类似浏览器 cookie 的机制。
因此,App 客户端需要在每一次通信时,携带标记信息。
服务端将依据该标记信息,进行用户身份认证。
1. 明文传输标记信息
例如,每次通信传输用户名和密码,或者传输手机号,邮箱等用户唯一标识数据。
# 读取 henry 博主的所有博客 http://api_host/blogs?name='henry'&password='123'缺点是显而易见的:在网络上传输,非常不安全。容易被篡改和攻击。
2. 密文传输标记信息
首先,服务端设计两个规则:
a. user_key 用户身份的唯一标记,等同于用户名。 用途:user_key 用在获取用户个人信息等 API 接口。 前提是该类信息不涉及用户个人隐私,如头像,昵称等。 优点:使用 user_key 密文,避免暴露系统内用户唯一标记。 取值:采用 uuid,Ruby 中可以这样取得
SecureRandom.uuid
b. token: 用户登录成功后的唯一标记,等同于用户名+密码。 将为每一个 token 设置一个过期时间,时长可以根据需求来定,如 30 天。 用途:token 用在每一个需要验证用户身份的请求中。 优点:避免每次验证用户身份,都输入用户名和密码; 若服务端用户身份信息修改,则对 token 做过期处理, 此时,任何 App 客户端的 token 都将过期,客户端将引导用户重新登录。 取值:采用20位的随机 base64 字符串,Ruby 中可以这样获得
SecureRandom.base64(length).tr('+/=lIO0', 'htdskrq') # 我们将容易混淆的“lIO0”这类字符进行了替换。 # 同时,对于 “+/=” 该字符,在 HTTP 请求的 URL 中会引起错乱,也进行了替换。
引入 user_key 和 token 之后,API 请求示例如下:
# 基于 user_key 和 token 进行身份认证,以及有效性验证 http://api_host/blogs?user_key='abacddc2-59a8-4eb0-a3be-ec20722db547'&token='1wWP1m1W739uRPsXgPo8'
HTTP 请求在网络传输中,容易被截取。 因此通信中的敏感数据,容易被泄露。
1. 基于 HTTPS 协议,进行加密传输
HTTPS 协议规则:报文中的任何东西都被加密,包括所有报头和荷载。
因此,攻击者截取请求后,所能知道的只有在两者之间有一连接这一事实。
所以,截取到了密文,也将无法识别。
特点:
处理策略简单,但是客户端和服务端程序调整较大; 安全性高,但是加解密性能开销大。
2. 基于证书
客户端和服务端基于证书,对需要传输的数据,进行加解密。特点:
传输的数据是密文; 只对请求的数据部分做加解密处理,资源开销较小; 获取数据,有证书,即可解密。 基于 “RSA公钥加密算法” 的 Ruby 代码示例如下:
require "openssl" require 'base64' require 'cgi' class RsaSignatureUtil PRIVATE_KEY = "your_private_key" PUBLIC_KEY = "your_public_key" class << self def sign(content) rsa_private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY) CGI.escape(Base64.encode64(rsa_private_key.sign(OpenSSL::Digest::SHA1.new, content))) end def verify(content, sign) rsa_public_key = OpenSSL::PKey::RSA.new(PUBLIC_KEY) rsa_public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(URI.unescape(sign)), content) end end end # 签名 parameters = "name='henry'&password='123'" sign = RsaSignatureUtil.sign(parameters) parameters << "&sign_type='RSA'" parameters << "&sign='#{sign}'" url = "http://api_host/blogs?#{parameters}" # 验签 content = "notify_data=#{params[:notify_data]}" sign = params[:sign] RsaSignatureUtil.sign(content, sign)
由于前面列举的 HTTP 请求在网络传输中,容易被截取。 因此请求容易被篡改和伪造。
1. 参数签名
签名和验签规则如下:
对网络请求中的数据部分,做参数签名; 请求服务端是,除了发送原有的数据之外,还携带着参数签名的值; 服务器端取到数据,基于同样的算法,对获取的数据做签名; 签名结果与客户端发送的签名做比较; 如果相同,则数据可信; 否则,说明数据被修改了。
由于纯粹的 HTTP 请求,在网络传输中极不安全。
因此,需要对涉及用户隐私,或者商业机密的数据做加密处理。
但是,加解密会提高程序的复杂度,同时增大系统的计算开销,以及增加前端和后端系统,连接调试的困难。
个人倾向于:有选择性的对 API 接口进行加密处理。
对于安全性要求较高的服务,使用 HTTPS 进行数据传输;
对于安全性要求一般的服务,可以使用简单的数据加密,参数签名等;
对于开放性的内容服务,可以使用明文。
2013-06-02