登录这件小事

登录是一个现代App或者网站都必备的功能,对于开发者来说,这件事情的核心问题是

  1. 我到底需不需要登录功能?
  2. 如果需要,那么在第三方登录和第一方登录之间如何做出选择
  3. 如何设计一个第一方的登录系统?

对于第一个问题,我觉得答案是肯定的,哪怕是一个单一功能的简单App,那么登录功能也是必须的。登录可以给你带来大量可分析的真实的用户数据,这种基于真实用户的数据要比从网上买来的dummy data更加适合一个企业对DT相关功能的探索和研发。同时登录功能的存在可以更加好的提供客户服务以及有利于数据传输的可靠性。在第三方登录和第一方登录这种问题上,第一方登录能够带来更大的可控性,第三方登录可以加快开发速度。第三方登录系统基本都是基于OAuth系列的,相信大部分开发者都比较熟悉了,毕竟工作中,大量POC都是需要用到第三方登录的。这篇文章将着重讨论如何设计和实现一个登录系统,同时,将涉及到一些诸如SSL加密通讯的相关话题。那么首先,对于前后端系统来说,到底什么叫做登录?

首先,作为大环境的要求,单纯的SSL证书加密和HTTPs是不足以应对今天更加复杂的网络威胁的。一般基本的要求都是,对于server2server的API必须是2-way SSL,而mobile App因为性能上做不了2-way SSL,所以只能做cert pinning。作为最基本的前提,我们先来说说mobile app的cert pinning是什么。Cert pinning基本思想就是,通过预存在本地的footprint来对比server发过来的cert data。对于企业来说,一般都会有一个只存在于内网环境或者Dev环境的cert server来提供cert给end dev:

%openssl s_client -showcerts -connect xxx.xxx.com:443 </dev/null 2>/dev/null|openssl x509 -outform DER > servercert.der

下载完了cert以后就可以单开一个project来将der文件转化为footprint:

const unsigned char *dbytes = [data bytes];
NSMutableString *hexStr = [NSMutableString stringWithCapacity:[data length]*2];
int i;
for (i = 0; i < [data length]; i++) {
  [hexStr appendFormat:@"0x%02x",dbytes[i]];
}

如果你将hexStr打印出来,将会在log里面看到类似“0x30,0x82.......”。复制粘贴这个string然后保存在一个[UInt8]里面,就可以直接使用了。

class WebServiceHandler:NSObject {
    fileprivate let footPrint = [0x30,0x80,0x40.............]
    
    func send<T:Request>(r:T,completion:DefaultCompletion) {
        ...
        session = URLSession(conmfiguration:config,delegate:self,delegateQueue:PrivateQ)
        ...
    }
}

extension WebServiceHandler:URLSessionDelegate {
    func urlSession(_ session:URLSession, didReceive challenge:URLAuthenticationChallenge, comoletionHandler:@escaping (URLSession.AuthChallengeDisposition, URLCredential?)->Void) {
        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else {
            assert(false,"authentication not match")
            return
        }
        guard let serverTrust = challenge.protectionSpace.serverTrust else {
            assert(false,"cert not found")
            return
        }
        guard SecTrustEvaluate(serverTrust, nil) == errSecSuccess else {
            assert(false,"cert not match")
            return
        }
        let count:CFIndex = SecTrustGetCertificateCount(serverTrust)
        for i in 0..<count {
            guard let certRef = SecTrustGetCertificateAtIndex(serverTrust, i) else {
                assert(false,"invalid server cert")
                continue
            }
            let certData = SecCertificateCopyData(certRef)
            let remoteCert = certData as Data
            let localCert = Data(bytes:footPrint)
            if localCert == remoteCert {
                completionHandler(.userCredential, URLCredential(trust:serverTrust))
                break
            }
        }
        ...
    }
}

这样我们首先完成了对所有App-Server通讯的SSL Cert Pinning的实现,然而这样并不代表我们就安全了,就可以明码传输数据了,数据还是进行加密处理的,比如信用卡卡号,用户登录的密码等等。对于这些数据的加密,目前主流方案是使用sha256对数据进行加密处理,对于iOS平台,我们可以对写一个String的extension来实现数据加密:

extension String {
    func encriypt()->String {
        if let stringData = self.data(using: .utf8) {
            return hexStringFromData(stringData as NSData)
        }
        assert(false,"sha256 failure")
        return self
    }
    private func digest(_ input:NSData) -> NSData {
        let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
        var hash = [UInt8](repeating:0,count:digestLength)
        CC_SHA256(input.bytes, UInt32(input.length),&hash)
        return NSData(bytes:hash,length:digestLength)
    }
    private func hexStringFromData(_ input:NSData) -> String {
        var bytes = [UInt8](repeating:0,count:input.length)
        input.getBytes(&bytes, length:input.length)
        var hexString = ""
        for byte in bytes {
            hexString += String(format:"%02x",UInt(byte)) 
        }
        return hexString
    }
}

到此为止,我们已经实现了最基本的登录功能,但是这样很明显还是存在安全隐患:这个authentication是可以绕过去的。也就是说,我不登录直接去使用其他API,那么我也是能够获得数据的。为了解决这个问题,authentication API在服务器端还需要生成一个one-time accessToken用作临时密码作为其他API验证用户的密码。这个accessToken将会持续一段时间,银行一般是15分钟,如果服务器在这段时间内没有收到新的request,这个token就会失效,用户必须重新登录来使用其他数据API。有的人会有疑问,你这不是重造轮子吗?我们已经有一个存在了好多好多年的东西叫做cookie!事实上,在实际App中token-based authentication远比cookie based流行,需要解释为什么,我们先需要解释另外一个概念叫做受信设备。

当App第一次被安装到设备上时,在使用任何API之前,会先使用deviceToken API来生成一个device ID来用作该设备的device ID。以后在调用任何API的时候,这个id将被作为header的一部分传递到server。如果这个id不存在,server将自动触发2-step authentication机制,比如向注册手机号发送动态验证码之类的。而对于受信任的设备,这个时候,用户可以选择指纹登陆,然后API还是会返回一个token用于其他API验证用户。

所以token based到底比cookie based到底好在哪里?最重要的一点是token-based 是stateless,在restful的大环境下,无状态依旧逐渐成为主流。因为这个token首先不需要储存在数据库当中因为是一次性的,其次和domain无关,最后在一些情况下将大幅减少服务器端所需要的操作。例如你的App是一个办公App,经理,职员,ceo的权限是不一样的,有了token,那么服务器端就不需要去验证权限,对比权限而只需要验证token本身是否有效。而cookie对移动端相对来说并不友好,一些老的API甚至压根不支持移动端访问,为了获得cookie你甚至需要使用stealth webView来获取cookie然后保存在本地供其他API使用。

总结一下,当点击登陆按钮的时候,到底发生了什么:

  1. 向服务器申请/调用本地device id
  2. 本地验证用户(指纹)/用户名-密码验证用户
  3. 得到服务器回传的token并保存为一个全局变量或者class 变量。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容