为效率而生:开源Mac版Google Authenticator认证客户端GoldenPassport 2017-03-05 14:17:21 最近运维同学为了提高安全性,用Google Authenticator对服务器加了双重认证,此后登录服务器需要先输入动态密码,在输入服务器密码。Google Authenticator相当于软token,对他不了解的同学可以看下这篇文章:[谷歌验证 (Google Authenticator) 的实现原理是什么?](https://www.zhihu.com/question/20462696)。 运维同学的出发点是好的,但是我原来写的各种自动登录服务器的脚本统统失效了。蛋疼的是我现在登录服务器的流程变成了: 1. 掏手机(我的是iPhone) 2. 解锁,碰上指纹解锁失败的情况还需要输入密码解锁 3. 打开Authenticator客户端,等待Verification Code更新大概1s 4. 记住Verification Code,然后到Mac端输入 5. 输入服务器密码,登录... 原本我只执行自己搞的一个命令就完事了,由于我经常需要登录各种不同的服务器,这种方式对工作效率的影响是可想而知的。 目前Google官方的客户端只有Android和iOS的,于是开始找找看有没有针对PC,发现有个针对Windows系统的WinAuth支持Google Authenticator,我无论工作、在家基本都用Mac,所以这个WinAuth我是没法用了,后来在GitHub上找到一个MacAuthenticator的工具,下载下来试用了一下基本能用,至少Mac端可以得到Verification Code不需要依赖手机了,但是依然解决不了效率问题,而且那个工具居然没法退出... 没个顺手的工具,看来还得我亲手开发个了,于是简单了设计了下我需要的功能: 1. 支持从二维码中直接识别Authentication Code,也就是otpauth协议中的那个secret 2. 支持Authentication Code管理,保存、添加、删除这些基本功能得有 3. 能够非常方便的得到我想要的Verification Code - 不需要手抄验证码,点击自动复制 - 支持全局快捷键直接填充验证码,不需要麻烦的点鼠标(我工作用触摸板,不用鼠标,比较依赖键盘) - 支持在shell脚本中获取验证码(只有这样,才能让我以前写的自动化工具正常工作) ## 技术调研 GitHub上已经有个MacAuthenticator开源项目了(基于OC的),所以技术实现上应该没什么障碍。 语言方面,因为14年的时候参与过[《The Swift Programming Language》](https://github.com/stanzhai/the-swift-programming-language-in-chinese)翻译(现在已经成为苹果官方指定的中文版本了),但是还从来没用过Swift,所以决定采用Swift开发,就当学习了。 otp协议方面,Google开源了其算法:[google-authenticator](https://github.com/google/google-authenticator),刚好也有个iOS版本的,是基于OC的,不过给Swift调用没啥问题,所以核心协议的处理直接拿来用就可以了。 如何将生成的Verification Code给其他应用调用?想来想去还是基于HTTP的调用起来比较简单,所以还需要实现一个内嵌的HTTP服务器,到[cocoapods](https://cocoapods.org/)上找了下,发现[Swifter](https://github.com/httpswift/swifter)比较适合。 macOS上的Application我确实是第一次接触,不过在Windows平台上开发过不少桌面类的应用,这块边学边做感觉问题不大(实际做的时候发现各种踩坑),在网上找了些快速入门的资料,发现一个非常棒的资料推荐一下:[WeatherBar](http://footle.org/WeatherBar/) ## 最终成果 [GoldenPassport](https://github.com/stanzhai/GoldenPassport)已经放到GitHub上了,项目主页有一个简单的使用说明,我这里就不介绍具体功能了,基本照着我的需求实现的。 几乎所有功能都在这个菜单里搞定了: ![](https://github.com/stanzhai/GoldenPassport/blob/master/screenshot/main.png?raw=true) 从二维码中识别OTP地址,没有二维码,自己手动输入也可以: ![](https://github.com/stanzhai/GoldenPassport/raw/master/screenshot/add-window.png) 和Shell脚本集成,全靠这个HTTP接口啦: ![](https://github.com/stanzhai/GoldenPassport/blob/master/screenshot/restful-api.png?raw=true) ``` # you can get the url from `http://localhost:17304/` code=$(curl 'http://localhost:17304/code/test@stanzhai.site') # ues the verification code echo $code ``` ## 技术点 开发过程中,踩了很多坑,遇到不少难点(主要是可参考的资料少),我这里简单的梳理下,对源码感兴趣的同学,直接去GitHub上Fork吧。 #### 基于Google的OTP库生成Verification Code ```Swift let data = OTPAuthURL.base32Decode(otpData.secret) let gen = TOTPGenerator(secret: data, algorithm: TOTPGenerator.defaultAlgorithm(), digits: TOTPGenerator.defaultDigits(), period: TOTPGenerator.defaultPeriod()) let code = gen?.generateOTP(for: Date()) // 这个code就是最终的结果啦 ``` #### 状态栏图标不清晰的问题 如果你的statusIcon是个18*18的png,参照网上的例子去弄的话,你会发现状态栏图标相当模糊,远不如系统自带的清晰,如果你用的png是个比较大的图片,你会发现状态栏中根本显示不下,解决这个问题的关键点是需要指定图片的大小。 ```Swift statusIcon = NSImage(named: "statusIcon") // 48 * 48的大小就可以了 statusIcon.size = NSMakeSize(20, 20) // 这是保证高清又能正常显示的关键 ``` #### 给状态栏按钮绑定事件 获取到系统状态栏按钮对象后,我需要绑定下点击事件,来显示菜单,折腾了许久才搞定,主要卡在action这个地方,网上关于这方面的资料是相当少,在Swift3中,我们创建一个Selector的正确姿势是`#selector(方法名)`同时必须指定`statusItem.target = self`才行。 ```Swift statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength) statusItem.target = self statusItem.action = #selector(openMenu) ``` #### 绑定全局快捷键 这方面的资料真的好少~ ```Swift let opts = NSDictionary(object: kCFBooleanTrue, forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString) as CFDictionary guard AXIsProcessTrustedWithOptions(opts) == true else { return } monitor = NSEvent.addGlobalMonitorForEvents(matching: .keyDown, handler: self.handleKeydownEvent) ``` #### 窗口默认居中显示 ```Swift httpPortConfigWindow.showWindow(nil) httpPortConfigWindow.window?.makeKeyAndOrderFront(nil) httpPortConfigWindow.window?.center() NSApp.activate(ignoringOtherApps: true) ``` #### 不同组件间消息交互 Foundation库为我们提供了一个基于观察者模式的`NotificationCenter`,用起来相当方便。 ```Swift // 组件A监听消息 let notificationCenter = NotificationCenter.default notificationCenter.addObserver(self, selector: #selector(verifyCodeAdded), name: NSNotification.Name(rawValue: "VerifyKeyAdded"), object: nil) // 组件B发送消息 let notificationCenter = NotificationCenter.default notificationCenter.post(name: NSNotification.Name(rawValue: "VerifyKeyAdded"), object: nil) ``` #### 复制内容到剪贴板 ```Swift let pasteboard = NSPasteboard.general() pasteboard.clearContents() pasteboard.setString(codeInfo.value, forType: NSStringPboardType) ``` #### 调用系统打开窗口,只允许选择图片类型 ```Swift let openPanel = NSOpenPanel() openPanel.allowedFileTypes = NSImage.imageTypes() ``` #### 从文件中识别二维码 网上大部分都是iOS扫二维码的示例,OSX下从文件中识别的方法,摸索了好一阵子才实现。 ```Swift let ciImage = CIImage(contentsOf: openPanel.url!) let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyLow]) let results = detector?.features(in: ciImage!) if (results?.count)! > 0 { let qrFeature = results?.last as! CIQRCodeFeature let data = qrFeature.messageString // 识别后的数据 ... } ``` ## 收获 GoldenPassport是我开发的第一个macOS Application,对桌面应用的开发流程算是清楚了,搞个窗口类的应用已无大碍。和Windows的桌面应用开发体验相比,感觉OSX的还是差了不少,这也跟自己不熟悉OSX有关吧。 踩了不少Swift语法的坑,现在用的是Swift3,网上找的一些资料不一定是针对Swift3的代码,拿过来不一定用,Swift的这种兼容性问题还是挺让人讨厌的。Swift4也快要出来了,依然有兼容性问题。 由于对Cocoa框架不熟悉,不少NSXXX的API不知道咋用,另外NS的不少API在Swift下用法变掉了,多亏了GitHub,通过GitHub的代码搜索功能,可以找到很多别人项目里的示例代码,在结合Swift的语法,开发过程中碰到的一些功能性问题基本都能解决。 熟悉了Xcode的项目依赖管理工具:[cocoapods](https://cocoapods.org/)和[Swift Package Manager](https://swift.org/package-manager/),对于子子孙孙无穷尽也的项目依赖,熟悉下项目依赖管理工具还是非常有必要的。在GoldenPassport项目因为Swift Package Manager不支持混合语言的项目依赖管理,所以就用了cocoapods来管理项目依赖了。 GoldenPassport的核心功能是我利用周末整整2天多时间折腾出来的,有种参加黑马的感觉,逼着自己做自己不熟悉的东西,现学现做,看看短时间内到底能做成什么样,搞完那一刻成就感满满。 ## 结语 源码地址:[GoldenPassport](https://github.com/stanzhai/GoldenPassport),欢迎Star。 编译好的工具可以到GitHub的[releases](https://github.com/stanzhai/GoldenPassport/releases)中下载,如果这个工具能帮助到其他人,那就再好不过了。 引入Google Authenticator,导致效率变差的问题得以完美解决,我原来的自动化脚本也能正常使用了,这个项目算是告一段了。回过神,该继续研究大数据的东西去了 )逃... 非特殊说明,均为原创,原创文章,未经允许谢绝转载。 原始链接:为效率而生:开源Mac版Google Authenticator认证客户端GoldenPassport 赏 Prev 深度剖析Spark分布式执行原理 Next 纠正vim的使用习惯