发现VVebo的SSO登录在Webview使用了1Password, 觉得可以用运行时来实现.点击查看效果图

实现思路:

  1. 以苹果刚公开的工具UIDebuggingInformationOverlay查看微博SDK中的controller和view
  2. 以运行时method swizzling 替换系统viewWillAppear方法, 拦截到微博的控制器后, 在相应位置添加1Password按钮
  3. 1Password回调后以Safari调试此Webview,查看h5的elementID, 将账户密码添加进去, 自动登录

关于微博SDK, 登录接口就不详述了

就是要注意微博SDK是在iOS没有安装微博客户端的时候才会调用Webview登录

UIDebuggingInformationOverlay的使用

UIDebuggingInformationOverlay是苹果刚公开的UI调试工具
使用如下:

1
2
3
4
let overlayClass = NSClassFromString("UIDebuggingInformationOverlay") as? UIWindow.Type
_ = overlayClass?.perform(NSSelectorFromString("prepareDebuggingOverlay"))
let overlay = overlayClass?.perform(NSSelectorFromString("overlay")).takeUnretainedValue() as? UIWindow
_ = overlay?.perform(NSSelectorFromString("toggleVisibility"))

然后就可以在手机上看到对应的controller和view了:


是不是很神奇!
OK, 现在我们很方便的拿到了控制器和view的名字”WBSDKAuthorizeWebViewController” “WBSDKWebView”, 当然也有其他方式, 不多说.

swift中的method swizzling

swift3中dispatch_once_t 被取消了, 因此用全局常量来实现只执行一次swizzling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public let controllerMethodSwizzling: (UIViewController.Type) -> () = { viewController in
let originalSelector = #selector(viewController.viewWillAppear(_:))
let swizzledSelector = #selector(viewController.proj_viewWillAppear(animated:))
let originalMethod = class_getInstanceMethod(viewController, originalSelector)
let swizzledMethod = class_getInstanceMethod(viewController, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
extension UIViewController {
open override class func initialize() {
guard self === UIViewController.self else { return }
controllerMethodSwizzling(self)
}
func proj_viewWillAppear(animated: Bool) {
self.proj_viewWillAppear(animated: animated)
}
}

但是! 在initialize方法处报了个警告, xcode跟我讲这方法swift不一定调, 而且以后彻底不让在swift里用了.好吧, 那我在applicationDidFinishLaunchingWithOptions调总可以了吧.

1
2
3
4
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
controllerMethodSwizzling(UIViewController.self)
return true
}

调用OnePassword

现在我们拦截到了微博控制器的viewWillAppear方法了, 接下来就是一些基本操作了.
1Password的iOS代码地址https://github.com/agilebits/onepassword-app-extension

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
func proj_viewWillAppear(animated: Bool) {
self.proj_viewWillAppear(animated: animated)
let viewControllerName = NSStringFromClass(type(of: self))
if viewControllerName == "WBSDKAuthorizeWebViewController" {
let hasOnePassword = UIApplication.shared.canOpenURL(URL(string: "org-appextension-feature-password-management://")!)
guard hasOnePassword else {
return
}
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "onepassword"),
landscapeImagePhone: nil,
style: .done,
target: self,
action: #selector(onePassword(_:)))
}
}
func onePassword(_ sender: AnyObject) {
for view in self.view.subviews {
if NSStringFromClass(type(of: view)) == "WBSDKWebView" {
for subview in view.subviews {
let viewClassName = NSStringFromClass(type(of: subview))
if viewClassName == "UIWebView" {
let webview = subview as! UIWebView
OnePasswordExtension.shared().findLogin(forURLString: "weibo.com", for: self
, sender: sender, completion: { (success, error) in
if (success != nil) {
let info = success as! Dictionary<String, Any>
let username: String = info["username"] as! String
let password: String = info["password"] as! String
webview.stringByEvaluatingJavaScript(from: "document.getElementById('loginName').value = '\(username)'")
webview.stringByEvaluatingJavaScript(from: "document.getElementById('loginPassword').value = '\(password)'")
webview.stringByEvaluatingJavaScript(from: "document.getElementById('loginAction').click()")
}
})
break
}
}
break
}
}
}

在获取了账户和密码之后, 用Safari调试Webview可获得elementID, 然后以Webview执行js代码即可

这种场景不太常见, 实现技术都不难, 主要提供一个思路.

__原创文章, 转载请注明出处