美文网首页
Alamofire-安全策略

Alamofire-安全策略

作者: yahibo | 来源:发表于2019-08-28 18:24 被阅读0次

    web服务器和服务器通信的时候,使用https连接是非常重要的,能够对数据加密传输、身份认证。https协议需要到ca申请证书,部署到服务器,应用端连接都是对该链接受信任的。证书可申请也可以自签,自签证书需要客户端验证通过才能访问。

    一、HTTP协议

    HTTP是互联网的基础协议,默认端口80,为满足应用需求HTTP也在不断的版本升级改进,从0.9版本1.1版本功能不断的强大起来。HTTP演变可参考:http://www.ruanyifeng.com/blog/2016/08/http.html

    HTTP的特点:

    • HTTP客户端请求只需要确定请求方法和路径。常用方法有GET、POST、HEAD,由于对参数封装形式不一样,一般获取数据使用GET,上传数据使用POSTGET参数暴露在链接中,POST则封装在请求体中
    • 使用灵活,可传输任意类型的数据,通过Content-Type标记传输的数据类型
    • HTTP协议是无状态协议,对处理过的事务无记忆能力,为了客户端服务端更好的交互,引用了CookieSession来存储请求中产生的状态

    工作流程:

    http.png

    不需要任何处理,客户端要服务端就给。

    二、HTTPS协议

    HTTPS协议为超文本传输安全协议,默认端口443HTTPS=HTTP+SSL,对数据进行加密,保护数据在交互时不被窃取,提高了对服务器恶意攻击及数据伪装的成本。使用该协议要求服务器申请证书并配置协议环境。

    HTTPS的工作流程:

    https.png
    • http、https请求都需要建立TCP三次握手连接,略《Socket》
    • ca颁发的证书加密为非对称加密,公钥加密,私钥解密,私钥加密,公钥解密。加密原理参考《RSA加密》

    需要申请证书,绑定域名,服务器中配置证书。一般个人会使用阿里的免费证书,虽然加密性一般,至少证书是受信任的。也可以自己创建证书,自建证书可以使用,但不会被信任,既然有免费的我们就走正规路线😁。

    分别发起http和https请求:

    http://www.yahibo.top
    https://www.yahibo.top

    通过Charles抓包观察数据如下:

    charles.png
    • 未加密请求直接抓取服务器响应的所有数据
    • 加密请求抓取到的是未知数据

    当然开启SSL代理还是可以抓到的:

    ssl.png

    因此在做APP的时候,为了防止APP被抓包,我们需要做反代理设置抓包,判断应用代理设置或证书验证。

    三、HTTP和HTTPS主要区别

    1. HTTPS协议需要到ca申请证书,个人颁发证书是不受信任的;

    2. HTTP是超文本传输协议,明文传输,HTTPS则是在HTTP上加了一层SSL(安全套接层),对数据加密传输;

    3. HTTP在服务器上的默认端为80HTTPS443端口

    4. HTTP连接是无状态的,HTTPS协议等价于HTTP+SSL/TLS,可进行加密传输、身份认证的网络协议。

    两者的优缺点很明显,HTTP不存在加密,明文传输,不安全,但传输速度快,HTTPS密文传输,传输中需要确定应用端和服务端的保密性和数据完整性。

    证书长什么样?

    ca申请的证书,包括两个文件.key文件.pem文件,一般在nginx配置文件中配置即可。.key是证书的私钥文件,.pem为证书文件。

    四、安全策略

    1、ServerTrustPolicyManager

    在初始化SessionManager中可以看到,有一个初始化类型设置了ServerTrustPolicyManager类对象,该对象就是安全策略的管理者。在实际开发中,APP可能会用到不同的主机地址host,根据业务不同需要要给不同的host设置不同的安全策略。因此管理者的实现如下:

    open class ServerTrustPolicyManager {
        public let policies: [String: ServerTrustPolicy]
        public init(policies: [String: ServerTrustPolicy]) {
            self.policies = policies
        }
        open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
            return policies[host]
        }
    }
    
    • 初始化了一个policies字典,keyhost,值为选择的响应的策略
    • 初始化方法中设置安全策略及ServerTrustPolicy
    • 通过serverTrustPolicy方法通过host获取ServerTrustPolicy对象

    不要问为什么分离一个manager来管理ServerTrustPolicy,不直接使用,因为这是大厂分工要明确,责任到每一层。

    1、ServerTrustPolicy

    ServerTrustPolicy是一个枚举类:

    public enum ServerTrustPolicy {
        case performDefaultEvaluation(validateHost: Bool)
        case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
        case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
        case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
        case disableEvaluation
        case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
         //代码省略若干
    }
    

    以上是类型关联,根据不同类型做处理。

    • performDefaultEvaluation默认策略类型,始终验证主机证书的有效性
    • performRevokedEvaluation对吊销过的证书做设置
    • pinCertificates验证服务器返回的证书的正确性,参数决定是否验证证书链
    • pinPublicKeys公钥验证
    • disableEvaluation无需验证,无条件信任
    • customEvaluation自定义验证,返回一个布尔值

    以上方法在项目中并没有默认配置,需要我们配置使用,经常会选择证书验证、公钥验证、不做验证模式。具体设置根据需要选择。

    2、发起请求

    实践出真知,我们发送一个https请求看看效果。

    let url = "https://www.yahibo.top/project/public/index.php?s=api/test/list"
    sessionManager.request(url).response {
        (response) in
        print(response)
    }
    

    运行如下:

    result.png

    运行一切正常,能够请求到数据,好像也没什么区别,其实上面已经做了数据传输的说明了加密验证。下面看一下抓包,开启Charles,重新发送请求。

    http请求一切正常。

    https请求运行结果如下:

    error.png

    http能够正常请求,https确报错,这个好像是我们想要的结果,都还没有配置相关信息😂。在AF中即是https在抓包状态下也是可以获取数据的,而Alamofire好像做了更多的处理,直接避免抓包。

    该安全策略其实对自签证书做的验证,既然我的证书是合法的被认可的就不做配置了😂。如果是自签证书需要对域名或证书做验证如下设置:

    let policies: [String: ServerTrustPolicy] = [
        "www.yahibo.top": .disableEvaluation
    ]
    let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
    return sessionManager
    
    • 不做验证,或者做以上提到的策略

    我以为的被验证错误,打脸😂。后续补充自签证书验证,确定是否能够通过请求。这是一个失败的实践,不过还是有收获的。

    ……

    五、网络监测

    分别看一下AFAlamofire的实现。

    AFNetworking-AFNetworkReachabilityManager

    枚举类型,确定网络状态:

    typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
        AFNetworkReachabilityStatusUnknown          = -1,
        AFNetworkReachabilityStatusNotReachable     = 0,
        AFNetworkReachabilityStatusReachableViaWWAN = 1,
        AFNetworkReachabilityStatusReachableViaWiFi = 2,
    };
    
    • 未连接网络、不可用网络、蜂窝网络、WiFi网络
    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
        [weakSelf setNetworkStates:status];
        switch (status) {
            case AFNetworkReachabilityStatusUnknown:
                NSLog(@"未知网络");
                break;
            case AFNetworkReachabilityStatusNotReachable:
                NSLog(@"这是一个不可达网络");
                break;
            case AFNetworkReachabilityStatusReachableViaWWAN:
                NSLog(@"这是一个蜂窝网络");
                break;
            case AFNetworkReachabilityStatusReachableViaWiFi:
                NSLog(@"这是一个WiFi网络");
                break;
            default:
                break;
        }
    }];
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    

    网络监测无非就是以上几个状态,设置回调代码块,具体监测行为由AFNetworkReachabilityManager完成。这里就简单看一下,下面看一下Alamofire的网络监测,其实都是一样的调用的底层api不变。

    Alamofire-NetworkReachabilityManager

    同样提供了这么一个类,来管理网络监测任务。对框架的学习,最大的收获就是能够更快一些的对源码做分析,快速使用框架。

    具体使用

    枚举类型:

    public enum NetworkReachabilityStatus {
        case unknown
        case notReachable
        case reachable(ConnectionType)
    }
    
    public enum ConnectionType {
        case ethernetOrWiFi
        case wwan
    }
    

    使用:

    let networkManager = NetworkReachabilityManager(host: "www.yahibo.top")
    networkManager?.listener = { status in
        switch status {
        case .unknown:
            print("未知网络")
            break
        case .notReachable:
            print("这是一个不可达网络")
            break
        case .reachable(.ethernetOrWiFi):
            print("这是一个WiFi网络")
            break
        case .reachable(.wwan):
            print("这是一个蜂窝网络")
            break
    }
    networkManager?.startListening()
    
    • 注意对象需要声明为全局,在作用域中声明会被销毁

    大同小异,编码方式变的更简洁了,这也符合swift的编码风格。以上可以看出在Alamofire中的网络监测将WiFi网络和蜂窝网络归为一类。下面看一下源码。

    1、属性

    1️⃣、声明一个闭包,在外部实现呢,网络变化时掉用闭包传值

    public typealias Listener = (NetworkReachabilityStatus) -> Void
    

    2️⃣、网络是否可达,包括蜂窝和WiFi网络

    open var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
    

    3️⃣、蜂窝网络是否为可达的

    open var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) }
    

    4️⃣、通过WiFi连接网络

    open var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) }
    

    5️⃣、获取网络类型

    open var networkReachabilityStatus: NetworkReachabilityStatus {
            guard let flags = self.flags else { return .unknown }
            return networkReachabilityStatusForFlags(flags)
    }
    

    6️⃣、设置监听闭包在哪个队列中调用你,默认给主队列

    open var listenerQueue: DispatchQueue = DispatchQueue.main
    

    7️⃣、定一个闭包类型的监听属性

    open var listener: Listener?
    

    初始化

    public convenience init?(host: String) {
        guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
        self.init(reachability: reachability)
    }
    
    • 传入服务器域名或ip,传入其他只要不为空也是可以获取到网络类型,还是设置为我们常用的服务地址比较好
    • 调用了系统方法来初始化一个网络监测对象,和AF中是一样的
    • 返回一个SCNetworkReachability对象,网络地址或名称的句柄
      SCNetworkReachabilityRef

    官方文档

    该对象可以确定当前主机的网络状态和目标地址的可达性,提供同步或异步接口,获取当前的网络状态。以上初始化有调用了一个init初始化方法:

    private init(reachability: SCNetworkReachability) {
        self.reachability = reachability
        // Set the previous flags to an unreserved value to represent unknown status
        self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30)
    }
    
    • 存储对象,并获取网络可达标识
    • 到此好像就已经初始化完成

    2、启动监听网络

    networkManager?.startListening()
    内部实现:
    open func startListening() -> Bool {
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = Unmanaged.passUnretained(self).toOpaque()
        let callbackEnabled = SCNetworkReachabilitySetCallback(
            reachability,
            { (_, flags, info) in
                let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
                reachability.notifyListener(flags)
            },
            &context
        )
        let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
        listenerQueue.async {
            guard let flags = self.flags else { return }
            self.notifyListener(flags)
        }
        return callbackEnabled && queueEnabled
    }
    
    • SCNetworkReachabilitySetCallback监听网络状态的变化,发生改变即调用该回调方法
    • listenerQueue在开启监听是调用一次
    • 通过notifyListener通知Listener闭包
    • 将任务添加在主线程中向外回调
    func notifyListener(_ flags: SCNetworkReachabilityFlags) {
        guard previousFlags != flags else { return }
        previousFlags = flags
        listener?(networkReachabilityStatusForFlags(flags))
    }
    
    • 先判断网络状态是否改变,如果没有变化不通知,有变化记录先前的网络flags
    • 通过listener闭包向外传递当前的网络状态
    func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
        guard isNetworkReachable(with: flags) else { return .notReachable }
        var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
    #if os(iOS)
        if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) }
    #endif
        return networkStatus
    }
    
    • 通过flags来获取当前的网络状态,首先看网络是否为可达网络
    • 通过系统属性对网络状态归类
    func isNetworkReachable(with flags: SCNetworkReachabilityFlags) -> Bool {
        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)
        let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic)
        let canConnectWithoutUserInteraction = canConnectAutomatically && !flags.contains(.interventionRequired)
        return isReachable && (!needsConnection || canConnectWithoutUserInteraction)
    }
    
    • 通过属性组合来确定网络是否可达

    3、关闭网络监听

    当结束后执行deinit方法来调用stopListening方法:

    open func stopListening() {
        SCNetworkReachabilitySetCallback(reachability, nil, nil)
        SCNetworkReachabilitySetDispatchQueue(reachability, nil)
    }
    
    • 将回调置空,队列置空即可

    其实网络监测并不复杂,只是对系统网络监测类的一个封装,设置枚举对网络状态归类,通过listener闭包向外发送网络监测数据。使用只需要初始化设置主机地址,然后开启监听即可。所谓的监听就是实现对网络状态监听的回调闭包,是框架和系统底层网络层的消息传递。

    学习原理及对底层源码探索是很有必要的,能够帮助我们快速开发,快速的解决问题。加油⛽️

    相关文章

      网友评论

          本文标题:Alamofire-安全策略

          本文链接:https://www.haomeiwen.com/subject/abbmectx.html