爲效率而生:開源Mac版Google Authenticator認證客戶端GoldenPassport

最近運維同窗爲了提升安全性,用Google Authenticator對服務器加了雙重認證,此後登陸服務器須要先輸入動態密碼,在輸入服務器密碼。Google Authenticator至關於軟token,對他不瞭解的同窗能夠看下這篇文章:谷歌驗證 (Google Authenticator) 的實現原理是什麼?git

運維同窗的出發點是好的,可是我原來寫的各類自動登陸服務器的腳本通通失效了。蛋疼的是我如今登陸服務器的流程變成了:github

  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不須要依賴手機了,可是依然解決不了效率問題,並且那個工具竟然無法退出...shell

沒個順手的工具,看來還得我親手開發個了,因而簡單了設計了下我須要的功能:swift

  1. 支持從二維碼中直接識別Authentication Code,也就是otpauth協議中的那個secret
  2. 支持Authentication Code管理,保存、添加、刪除這些基本功能得有
  3. 可以很是方便的獲得我想要的Verification Code
  • 不須要手抄驗證碼,點擊自動複製
  • 支持全局快捷鍵直接填充驗證碼,不須要麻煩的點鼠標(我工做用觸摸板,不用鼠標,比較依賴鍵盤)
  • 支持在shell腳本中獲取驗證碼(只有這樣,才能讓我之前寫的自動化工具正常工做)

技術調研

GitHub上已經有個MacAuthenticator開源項目了(基於OC的),因此技術實現上應該沒什麼障礙。安全

語言方面,由於14年的時候參與過《The Swift Programming Language》翻譯(如今已經成爲蘋果官方指定的中文版本了),可是還歷來沒用過Swift,因此決定採用Swift開發,就當學習了。服務器

otp協議方面,Google開源了其算法:google-authenticator,恰好也有個iOS版本的,是基於OC的,不過給Swift調用沒啥問題,因此核心協議的處理直接拿來用就能夠了。框架

如何將生成的Verification Code給其餘應用調用?想來想去仍是基於HTTP的調用起來比較簡單,因此還須要實現一個內嵌的HTTP服務器,到cocoapods上找了下,發現Swifter比較適合。運維

macOS上的Application我確實是第一次接觸,不過在Windows平臺上開發過很多桌面類的應用,這塊邊學邊作感受問題不大(實際作的時候發現各類踩坑),在網上找了些快速入門的資料,發現一個很是棒的資料推薦一下:WeatherBarcurl

最終成果

GoldenPassport已經放到GitHub上了,項目主頁有一個簡單的使用說明,我這裏就不介紹具體功能了,基本照着個人需求實現的。

幾乎全部功能都在這個菜單裏搞定了:

從二維碼中識別OTP地址,沒有二維碼,本身手動輸入也能夠:

和Shell腳本集成,全靠這個HTTP接口啦:

# 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

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是個比較大的圖片,你會發現狀態欄中根本顯示不下,解決這個問題的關鍵點是須要指定圖片的大小。

statusIcon = NSImage(named: "statusIcon")  // 48 * 48的大小就能夠了
statusIcon.size = NSMakeSize(20, 20)  // 這是保證高清又能正常顯示的關鍵

給狀態欄按鈕綁定事件

獲取到系統狀態欄按鈕對象後,我須要綁定下點擊事件,來顯示菜單,折騰了許久才搞定,主要卡在action這個地方,網上關於這方面的資料是至關少,在Swift3中,咱們建立一個Selector的正確姿式是#selector(方法名)同時必須指定statusItem.target = self才行。

statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength)
statusItem.target = self
statusItem.action = #selector(openMenu)

綁定全局快捷鍵

這方面的資料真的好少~

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)

窗口默認居中顯示

httpPortConfigWindow.showWindow(nil)
httpPortConfigWindow.window?.makeKeyAndOrderFront(nil)
httpPortConfigWindow.window?.center()
NSApp.activate(ignoringOtherApps: true)

不一樣組件間消息交互

Foundation庫爲咱們提供了一個基於觀察者模式的NotificationCenter,用起來至關方便。

// 組件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)

複製內容到剪貼板

let pasteboard = NSPasteboard.general()
pasteboard.clearContents()
pasteboard.setString(codeInfo.value, forType: NSStringPboardType)

調用系統打開窗口,只容許選擇圖片類型

let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = NSImage.imageTypes()

從文件中識別二維碼

網上大部分都是iOS掃二維碼的示例,OSX下從文件中識別的方法,摸索了好一陣子才實現。

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的項目依賴管理工具:cocoapodsSwift Package Manager,對於子子孫孫無窮盡也的項目依賴,熟悉下項目依賴管理工具仍是很是有必要的。在GoldenPassport項目由於Swift Package Manager不支持混合語言的項目依賴管理,因此就用了cocoapods來管理項目依賴了。

GoldenPassport的核心功能是我利用週末整整2天多時間折騰出來的,有種參加黑馬的感受,逼着本身作本身不熟悉的東西,現學現作,看看短期內到底能作成什麼樣,搞完那一刻成就感滿滿。

結語

源碼地址:GoldenPassport,歡迎Star。

編譯好的工具能夠到GitHub的releases中下載,若是這個工具能幫助到其餘人,那就再好不過了。

引入Google Authenticator,致使效率變差的問題得以完美解決,我原來的自動化腳本也能正常使用了,這個項目算是告一段了。回過神,該繼續研究大數據的東西去了 )逃...

相關文章
相關標籤/搜索