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


最近運維同學為了提高安全性,用Google Authenticator對服務器加了雙重認證,此后登錄服務器需要先輸入動態密碼,在輸入服務器密碼。Google Authenticator相當於軟token,對他不了解的同學可以看下這篇文章:谷歌驗證 (Google Authenticator) 的實現原理是什么?

運維同學的出發點是好的,但是我原來寫的各種自動登錄服務器的腳本統統失效了。蛋疼的是我現在登錄服務器的流程變成了:

  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》翻譯(現在已經成為蘋果官方指定的中文版本了),但是還從來沒用過Swift,所以決定采用Swift開發,就當學習了。

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

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

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

最終成果

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM