’HTTP(S)‘

11 篇文章 0 订阅

updating//实时(长连接/轮询),可靠,公平性

 

http协议:方便并行化,请求-响应模式的双向通信,其报文头不定长且可任意扩展,文本格式描述报文头并用双换行分隔报文头和内容。.1.1始的keep-alive

域名解析(IP直连) --> 发起TCP的3次握手(共识) --> 建立TCP连接后发起http请求( HEAD: 仅请求响应首部  PUT(webdav) : 修改,DELETE(webdav) ,  OPTIONS:返回请求的资源所支持的方法  TRACE(不能由浏览器发出): 追求一个资源请求中间所经过的代理,可远程诊断服务器。HTTP req是无状态的 ( session(客户端与服务器端的 交互状态信息,被保存在服务器端,比如内存,数据库等,每个session都有一唯一标识,由服务器生成,这个标识也要在客户端进行保存,如通过cookie保存session id,在请求时发送给服务器:如通过表单的隐藏字段携带session id与服务器通信,方便服务器判断客户端的状态。session共享问题:在分布式应用中,Server一般都架在反向代理或是负载均衡设备后面,会面临:同一用户的多个请求可能被分发到不同的机器,若把session保存在了本地内存,就无法在多个机器间共享用户的session。可把session存放到分布式的内存(eg:memcached)或是集中式存储中(eg:database);或在反向代理或负载均衡设备上把相同用户的请求分发到同一台机器(机器宕机后请求重新分配)和cookie (标识用户状态,在客户端保持状态化信息,每个cookie内容都属于特定的域(domain)和路径(path),出于安全考虑,不同域或路径下的cookie不能共享;会话cookie:没有指定过期时间,保存在内存,浏览器关闭后就失效;持久cookie:指定了过期时间,保存在浏览器本地) ) :如用户认证要记录用户登录状态,购物车应用要记住用户选择的商品,广告投放应用要记录用户的历史浏览行为等 ) --> 服务器响应http请求:1xx: 信息性100, 101;2xx:成功200:OK;3xx: 重定向  301: 永久重定向, Location响应首部的值仍为当前URL,因此为隐藏重定向,  302: 临时重定向,显式重定向, Location响应首部的值为新的URL, 304:Not Modified  未修改,如本地缓存的资源文件和服务器上比较时,发现并没有修改,可直接使用缓存;4xx: 客户端错误,404: Not Found  请求的URL资源不存在;5xx: 服务器端错误,500: Internal Server Error  服务器内部错误,502: Bad Gateway  前面代理服务器联系不到后端的服务器,504:Gateway Timeout  代理能联系到后端的服务器但后端的服务器在规定的时间内没有给代理服务器响应 -->4次挥手

 


HTTP headers请求头和响应头:
。.缓存控制:如果我们的静态资源使用了CDN,设置了缓存就可在CDN节点上保存一份文件,减少CDN的回源次数。对不常改变的内容在客户端进行缓存:Cache-Control(HTTP1.1)/Pragma(HTTP1.0):指示客户端是否缓存以及缓存时长。Public、no-cache(不建议使用本地缓存地缓存) 、no-store(不缓存),默认private,把内容缓存在用户私有空间,如:Cache-Control:max- age(相当Cache-Control:public/private  Expires:当前客户端时间 + maxAge。而 Cache-Control:no-cache 和 Cache-Control:max-age=0相当)=86400,must-revalidate,要客户端所请求的资源缓存一天(max-age单位秒,相对时间);Expires:客户端缓存有效期(若不强制刷新),注:优先级Cache-Control > Expires;不同客户端的不同行为(刷新,后退,地址栏回车等)在实现上可能有差异;
Last-Modified/If-Modified-Since:Last-Modified是服务器端返回给客户端的资源最后修改时间戳(必须是格林威治(GMT)时间,比如:Last-Modified:Sat, 19 Oct 2013 09:20:15 GMT),客户端在下次请求时(比如强制刷新)会带上If-Modified-Since参数来校验资源是否有更新,没有更新的话服务器就返回304,客户端直接取本地缓存(虽然可能是过期的)的资源,这时就只有请求开销,无网络传输,否则返回200重新请求数据并刷新缓存。
ETag/If-None-Match:ETag是根据文件属性通过一定算法生成的资源标识,也是用来确定客户端请求的资源是否有更新。如果服务器返回了一个ETag值给browser客户端,那么下次客户端请求时会带上If-None-Match参数来校验资源是否更新,没有更新的就返回304状态码(效果基本等同于Last-Modified).
:ETag需要计算,对于计算资源紧张的服务器来说是一种消耗,所以有些不使用ETag;如果服务器在负载均衡后面,同一个资源的请求可能分发到不同的后端机器上,由于ETag的计算依赖于文件属性,不同机器上内容相同的文件可能生成的ETag不同,这样就可能使本来内容没变的文件通过ETag校验失败。这里有两种解决方案:一是etag计算不依赖于本地机器,比如直接算文件内容的md5值;二是在负载均衡器上把相同的url请求分发到同一台后端机器。
 

。.断点请求:Accept-Ranges:服务端支持断点下载时会返回这个响应头给客户端,当客户端知道这个后就可以发送断点请求了。Content-Length:响应信息的长度,注:用head方法提交请求时不会返回具体数据,但是Content-Length会返回完整数据的大小。Range/Content-Range:客户端请求时提交名为Range的header,告诉服务器自己要请求哪部分的数据,如:Range: bytes=0-1023表示请求第0到1023个字节.然后服务器返回这1024个字节给客户端,响应头中会带上Content-Range,即:Content-Range: bytes 0-1023/4096,4096是文件总大小。客户端下次请求可以从第1024个字节处开始,Range: bytes=1024-xxxx
 

。.编码:Accept-Encoding/Content-Encoding:前者是客户端支持接收的消息编码类型。默认是identity,可选值有gzip,compress等。后者是服务器端响应信息的内容编码类型,常用的就是压缩。Content-Encoding: gzip,deflate,compress.可对响应结果压缩传输。
Transfer-Encoding:response header.响应消息的传输编码类型,规定了网络传输的形式。一般:Transfer-Encoding: chunked。当服务器产生动态内容,不知道响应信息的具体长度时,可以通过这个指定分块进行传输,处理多少数据就返回多少数据,这样不用等到数据都准备好了一次性返回。结合上面的内容编码,如gzip,可以分块压缩并进行传输。注:使用这种编码传输时,得不到Content- Length的,因为内容还没完全生成。
 

。.X-Forward-For:request header. 用来标识用户的真实ip,特别是通过代理(正向或反向)访问服务器或是服务器在负载均衡设备后面的情况。:X-forward-For: client,proxy1,proxy2,…最左边的是最接近客户端的ip。
User-Agent:request header.服务器用来识别客户端基本信息。可用来识别搜索爬虫的时候有用,也可做客户端的统计。

Referer:request header.客户端访问服务器时,这个Referer来指定请求来源,比如是从哪个网站链接过来的,可用来做统计。在需要资源防盗链的场景中来过滤非法的请求来源(注:referer可被客户端伪造)。
Location:response header.在301/302状态码的响应头中,都会带上它,来指示客户端用新的地址去访问需要的资源。
Connection:request/response header.在http/1.1中,客户端和服务端默认都是保持连接的,即Connection: keep-alive.如果任何一方不想保持连接,都可以把这个值设置为close。在服务端可能要做更多的设置,比如连接keep-alive的时间,服务器内核的一些网络参数设置(针对TCP)。

 

 

HTTPS:同时使用了对称加密和RSA(RSA算法:将两个大素数的乘积作为公钥,因为将其分解因式极其困难)非对称加密 (1对多通信实现身份认证和密钥协商) (基于散列函数验证信息的完整性:MD5、SHA1、SHA256,函数不可逆、对输入敏感、输出长度固定,对数据的任何修改都会改变散列函数的结果) :客户端先向服务器发起请求(以明文传输请求信息,含SSL版本信息,加密套件候选列表: 认证算法 Au (身份验证)、密钥交换算法KeyExchange(密钥协商)、对称加密算法Enc (信息加密)和信息摘要 Mac(完整性校验)、压缩算法候选列表、随机数:用于后续生成密钥、扩展字段等),服务器将自己的公钥(用于后面加密客户端私钥)发送给客户端。客户端收到公钥后,验证( 避免连接不法服务器,防中间人攻击,用客户端设备内置的CA的公钥能否解密数字证书(因为其有效期,可考虑验证根CA证书,是否吊销 revocation,域名) )服务器发送的数字证书(CA对公钥认证:CA本身有一对公钥和私钥,CA会用自己的私钥对要进行认证的公钥(明文)加密,得到的密文再加上证书的过期时间、颁发给、颁发者等信息组成了数字证书)的合法性(如安卓设备(Security with HTTPS and SSL):证书文件流-> Certificate ->KeyStore -> TrustManagerFactory -> TrustManager[] -> SSLContext -> SSLSocketFactory -> HttpsURLConnection),过检客户端会生成一个随机值,即对称加密的密钥(用于加密需传输至服务端的数据),然后用服务器的公钥对客户端密钥加密,HTTPS中的第一次HTTP请求结束;第二个HTTP请求,将加密之后的客户端密钥发送给服务器,用服务器的私钥解出的客户端密钥对数据对称加密(用客户端的密钥)传送给客户端。

SSL(TLS): 对称加密又私钥加密:算法公开、加,解密速度快,适合大数据量: 明文 + 加密算法 + 私钥 => 密文; 密文 + 解密算法 + 私钥 => 明文;     非对称加密又公钥加密:安全性好于前者,弊如加/解密费时,适合少量数据场景。

公钥和私钥成对(锁与钥匙): 明文 + 加密算法 + 公钥 => 密文, 密文 + 解密算法 + 私钥 => 明文; 明文 + 加密算法 + 私钥 => 密文, 密文 + 解密算法 + 公钥 => 明文。

      @Override  public void run() {  
            HttpsURLConnection conn = null;    
            InputStream is = null;
            try {
                URL url = new URL("https://");
                conn = (HttpsURLConnection)url.openConnection();

                CertificateFactory cf = CertificateFactory.getInstance("X.509"); //实现(空实现则默认过检,可建立HTTPS连接)X509TrustManager
                InputStream cerInputStream = getAssets().open("SRCA.cer"); //根CA证书有效期长
                Certificate ca; //java.security.cert.Certificate
                try {
                    ca = cf.generateCertificate(cerInputStream);//    Log.e("ca=",((X509Certificate) ca).getSubjectDN());
                } finally {
                    cerInputStream.close();
                }

                
                String keyStoreType = KeyStore.getDefaultType(); 
                KeyStore keyStore = KeyStore.getInstance(keyStoreType);
                keyStore.load(null, null);
                keyStore.setCertificateEntry("ca", ca); 


                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
             
                tmf.init(keyStore);   
                TrustManager[] trustManagers = tmf.getTrustManagers();


                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, trustManagers, null); 


                SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
                conn.setSSLSocketFactory(sslSocketFactory);


                is = conn.getInputStream();
                final String str = getStringByInputStream(is);
    
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(conn != null){
                    conn.disconnect();
                }
       }     

OkHttp预设服务端证书的pining 值OkHttp.Builder.certificatePinner。客户端持有服务端的公钥证书,并持有自己的私钥,服务端持有客户的公钥证书,也有自己(和客户端)私钥,连接与请求时对比。

OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory())传递证书,用于双向认证。

 

  1. 首先,客户端去请求服务端的数字证书,这个证书包含了一个公钥。该证书购买后存储于我们自己服务器上。
  2. 当服务端收到客户端请求后,会把这个数字证书回传给客户端,由于是公钥,所以不害怕被窃取。
  3. 客户端收到数字证书后,先去验证证书的真实性。如果验证通过,就会从里面取出一个公钥
  4. 客户端本地生成一个随机数,作为未来的会话私钥,利用前面的公钥进行加密
  5. 客户端把加密后会话私钥回传给服务端,在这个过程中,即使加密后的会话私钥被窃取也不用担心,因为中间人并没有解密私钥,所以读不出里面的会话私钥
  6. 服务端接收到加密会话私钥后,利用从CA购买证书时获得的解密私钥进行解密读出真实会话私钥。至此,客户端与服务端同时拥有了一个只有它们二者知道的会话私钥,非对称加密连接建立完成。
  7. 一旦客户端和服务端连接建立起来后,未来的数据通信都利用这个会话私钥进行对称加密传输数据。

采用了https后,我们所有网络传输的数据都由明文变成了密文,即使中间有人能够监听到数据包,也不能轻易获取user的帐户密码信息。

实际上,在步骤3用户需要去验证数字证书时,如果这个验证过程被欺骗了呢?如果在最开始,攻击者就拦截掉客户端与服务端的通信。当客户端在请求证书时,攻击者回传一个他自己的假证书,而且攻击者已经通过其他手段欺骗用户在手机上信任了这个假证书,那么当客户端接收到证书并去验证时,是可以通过的

这也就意味着,一旦客户端遭受这样的攻击,未来客户端都会与一个虚假的中间人通信,而且中间人也可以拿着客户端传来的信息去与我们的服务端通信,而这个过程客户端和我们服务端完全不知道中间人的存在

公钥绑定(SSL Pinning)

为了防止客户端被虚假证书欺骗,可以采取的方式是把我们自己的公钥直接绑定给客户端,当客户端收到证书后,与提前绑定好的公钥进行验证,从而防止虚假证书的入侵。

在Android端,我们利用OkHttp3提供的CertificatePinner实现公钥绑定OkHttpClient client = new OkHttpClient.Builder()  
  .certificatePinner(
        new CertificatePinner.Builder().add("your_host", "your_public_key").build())
        .build();

 

 

Token机制

回到注册流程。当服务端拿到用户名密码后,会去创建一个新的user,同时我们会基于用户相关信息生成一个Token并回传给客户端。客户端在接收到Token后需要在本地进行存储。另外,由于每个http请求都是无状态的,因此未来客户端如果想把user信息传递给服务端时,就必须通过Token来传递,才能识别出某个请求的来源。

那么,我们应该如何在Android和服务端的代码里具体实现Token的传递解析有效性验证机制呢?

1. 首先在Android端,为了把Token信息存入到所有请求的http header里,我们采用了okhttp3提供的interceptor接口来。

OkHttpClient client = new OkHttpClient.Builder()  
        .addInterceptor(new Interceptor() {
          @Override
          public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Request.Builder newRequestBuilder = request.newBuilder();
            String token = getAuthToken();
            if (!TextUtils.isEmpty(token)) {
              newRequestBuilder.addHeader("Authorization", token);
            }

            Request newRequest = newRequestBuilder.build();
            return chain.proceed(newRequest);
          }
        })
        .build();

2. 然后在服务端,我们需要解析客户端传递过来的Token信息并进行校验。这里可以创建一个pythondecorator方法:

def mobile_request(func):  
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        kwargs = kwargs if kwargs or {}
        if request.headers.get('Authorization'):
            encrypted_token = request.headers.get('Authorization')
            isValid, user_info = check_token(encrypted_token) //解析并验证token有效性
            if not isValid:
                abort(498) //token无效,返回498状态码
            user = get_user(user_info)
            if not user:
                abort(403) //找不到user,返回403状态码
            kwargs['user_info'] = user_info //成功解析出user_info
        return func(**kwargs)
    return wrapped        
@app.route("/www/index")
@mobile_request // 使用decorator包装方法
def get_user(**kwargs):  
    user_info = kwargs['user_info'] // 取出decorator中封装好的user_info
    return db.get_user(user_info) // 利用user_info进行逻辑处理

3. 最后,请求结果返回到客户端,如果通过监测状态码发现返回结果是与Token相关的error/异常,则表示Token失效,此时我们让用户强制重新登录,生成新Token。这一步仍然可以在上面的interceptor里进行。

OkHttpClient client = new OkHttpClient.Builder()  
        .addInterceptor(new Interceptor() {
          @Override
          public Response intercept(Chain chain) throws IOException {
            ... //put token into newRequest
            Response response = chain.proceed(newRequest); // 获取服务端返回结果
            switch(response.code()) {
              case ResponseCode.USER_NOT_FOUND: // 状态码: 403 找不到user
                  eventBus.post(new UserNotFoundEvent()); // 强制logout
                  break;
              case ResponseCode.TOKEN_EXPIRED: // 498 token失效
                  eventBus.post(new TokenExpiredEvent()); // 强制logout
                  break;
              default:
                  break;
            }
            return response;
          }
        })
        .build();

至此,我们完成了Android端和服务端的Token传递、解析和失效处理。

在完善了Token的管理机制后,我们未来的http请求中只要带上这个Token,就可以畅通无阻地去服务端做与自身user相关的各种操作了。

那么,既然Token像家里门禁卡一样,只要拥有就能进入我们服务端并获取这个特定user的所有数据。那也就意味着,一旦攻击者窃取了某个user的Token,那在Token失效前,攻击者随时可以利用这个Token获取这个user的一切信息。

遇到Token被盗,该怎么办呢?

调整Token过期时间

针对Token被盗这种威胁,我们可以缩短Token的过期时间的方法。这样即使一个Token泄漏了,在一段时间后,这个Token也会自动失效。当然这也做会需要用户频繁登录获取新Token;而且失效前的这段时间内,攻击者仍然是可以直接连上服务端随意获取数据的。

Request签名

这种方法也是OAuth推荐的一种方法,其原理是在客户端和服务端统一好某种加密方法和一个密钥,这个密钥同时存储在客户端和服务端。每次客户端准备发起一个请求时,利用这种加密算法和密钥,针对该请求的API和参数进行计算得到一个数,称之为这个Request的签名,然后我们把这个签名放入到Request中。当服务端接收到Request后,就可以利用相同的加密算法和密钥来验证其中签名的真实性。

OkHttpClient client = new OkHttpClient.Builder()  
        .addInterceptor(new Interceptor() {
          @Override
          public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            String sign = RequestSignUtil.sign(request);
            HttpUrl url = request.url().newBuilder()
        .addQueryParameter("request_sign", sign)
        .build();
            Request newRequest = request.newBuilder().url(url).build();
            return chain.proceed(newRequest);
          }
        })
        .build();

通过对每一个Request签名,可以确保服务端接收到的所有Request都来自我们自己的客户端。即使有人得到了Token想伪造Request,他也不知道如何计算Request签名,从而减小了Token被盗的危害。

当然,每种安全方法都有漏洞,Request签名的方法意味着我们必须在客户端保存好加密算法和密钥,可以通过代码混淆、密钥存储到.so文件等方法来提高破解难度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值