swift - macOS 应用程序中使用 Wkwebview 的 Facebook 手动登录流程失败
问题描述
WKWebView
没有按需要执行“从 Facebook 登录”工作,因为浏览器似乎总是记住以前用于登录的帐户凭据,即使我尝试在发出登录请求之前和之后清除 cookie。
我正在按照此处的指南为 MacOS 项目手动构建 Facebook 登录流程:
https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/
我决定使用嵌入式 Web 浏览器 ( WKWebView
) 来处理登录。我想要实现的是,当用户单击“从 Facebook 登录”按钮时,WKWebView
会以表格形式呈现,加载 https://www.facebook.com/v3.3/dialog/oauth?client_id= [my-app-id]&redirect_uri=https://www.facebook.com/connect/login_success.html&response_type=token请求,显示对话框让用户输入他的电子邮件和密码,并且在用户成功登录后它redirect_uri
使用令牌获取,关闭自身以完成登录流程。
第一次在新计算机上执行此过程,它工作正常。但是一旦我以一个用户的身份成功登录,浏览器似乎会自动记住帐户凭据,并且下次我点击“从 Facebook 登录”时,它会显示然后立即关闭,这意味着它直接跳转到 redirect_uri 与附加到 uri 的前一个用户的令牌。
我没有太多关于程序如何使用缓存或如何存储用户凭据的先验知识,但我认为它与 cookie 有关。所以我做了
HTTPCookieStorage.shared.removeCookies(since: .distantPast)
和
facebookLoginRequest.httpShouldHandleCookies = false
在我认为合适的地方。
这些操作有所作为,但仍然没有解决问题。浏览器不会“在”它出现后立即关闭,但它仍然可能会在 10 秒后获得带有前一个用户令牌的重定向 uri,然后自行关闭(中断正在进行的凭据输入行为当前用户)并以以前的用户身份登录我的应用程序。
然后我检查了程序正在加载的请求(它如何在 url 之间跳转),它是:
1. https://www.facebook.com/v3.3/dialog/oauth?client_id=2390673297883518&redirect_uri=https:// www.facebook.com/connect/login_success.html&response_type=token
2. https://www.facebook.com/login.php?skip_api_login=1&api_key=2390673297883518&kid_directed_site=0&app_id=2390673297883518&signed_next=1&next=https%3A%2F%2Fwww.facebook.com%2Fv3.3%2Fdialog%2Foauth%3Fclient_id%3D2390673297883518%26redirect_uri%3Dhttps%253A%252F%252Fwww.facebook.com%252Fconnect%252Flogin_success.html%26response_type%3Dtoken%26ret%3Dlogin%26fbapp_pres%3D0%26logger_id%3Da6eb9f6e-66d4-41e3-a5f2-dc995c98183e&cancel_url=https%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%23_%3D_&display=page&locale=en_US
3. https://www.facebook.com/common/referer_frame.php
4. https://www.facebook.com/v3.3/dialog/oauth?client_id=2390673297883518&redirect_uri=https%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html&response_type=token&ret=login&fbapp_pres=0&logger_id=a6eb9f6e-66d4-41e3-a5f2-dc995c98183e
5. https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&next=https%3A%2F%2Fwww.facebook.com%2Fv3.3%2Fdialog%2Foauth%3Fclient_id%3D2390673297883518%26redirect_uri%3Dhttps%253A%252F%252Fwww.facebook.com%252Fconnect%252Flogin_success.html%26response_type%3Dtoken%26ret%3Dlogin%26fbapp_pres%3D0%26logger_id%3Da6eb9f6e-66d4-41e3-a5f2-dc995c98183e&lwv=100
6. https://www.facebook.com/v3.3/dialog/oauth?client_id=2390673297883518&redirect_uri=https%3A%2F%2Fwww.facebook.com%2Fconnect%2Flogin_success.html&response_type=token&ret=login&fbapp_pres=0&logger_id=fa7e695e-3469-43f5-9b79-75c6130824b0&ext=1564027752&hash=AebnuofbfCPezaKn
7. https://www.facebook.com/connect/login_success.html#access_token=EAAXXXX&data_access_expiration_time=1571800153&expires_in=5104236
1、2、3 总是发生。4 是当浏览器打断您时加载的内容,自动将您记录为以前的用户,然后自行关闭。它并不总是发生,或者至少它并不总是在相同的时间后发生,所以这对我来说是非常不可预测的。我认为这可能与清除 cookie 有关,但我不知道如何。
如果加载了 4,则永远不会加载 5 和 6,我们直接跳转到 7(具有前一个用户的访问令牌),这是在完整、成功登录后应该加载的内容。
然后我所做的就是试图阻止 4。
if let urlStr = navigationAction.request.url?.absoluteString {
let block = urlStr.range(of: "&ret=login&fbapp_pres=0&logger_id=")
let allow = urlStr.range(of: "&hash=")
if (block != nil && allow == nil) {
print("not ok")
print(urlStr)
decisionHandler(.cancel)
} else {
print("should ok")
print(urlStr)
decisionHandler(.allow)
}
所以 4 永远不会被加载。我每次都必须完成“输入我的电子邮件和密码并单击登录按钮”操作。但这仍然不能解决问题,因为在我点击登录后,只有不到一半的时间,Facebook 会给出以下消息:“出了点问题。请尝试关闭浏览器并重新打开它。” 然后我将不得不退出我的应用程序并重新打开它,有时它可以正常工作,有时它仍然不能,所以我需要退出并重新打开它。
当我单击登录按钮时,总是会加载 5。但是当它成功时,6 和 7 也会被加载;当它失败时,它会卡在 5,没有到达 6 和 7,并弹出“出现问题”消息。
出现错误消息的屏幕截图:
正如您在屏幕截图中看到的,在“出现问题”消息下,我的 Facebook 主页也已加载。但是,加载的 Facebook 页面不一定属于我在单击登录之前刚刚输入其凭据的用户,而是属于前一个用户(我使用多个帐户进行了测试)。如果加载的 Facebook 主页和刚刚尝试登录的用户不匹配,Facebook 可以检测到这种不匹配,然后弹出“请先登录”消息并强制用户返回登录对话框。
这意味着即使我试图阻止 4,它并没有真正阻止程序以以前的用户身份登录。它可能只是阻止了 URL 在用户界面中加载和显示。我也在网上搜索,据说当用户已经登录但再次发出另一个登录请求时会显示“出现问题”消息。
所以我想困扰我的核心问题仍然是,我到底怎样才能让程序停止存储前一个用户的凭据?这种存储机制是如何工作的?非常感谢任何能帮助我解决这个问题的人。我的网络知识很差,在发出初始 HTTP 请求后在这里发生的事情对我来说就像一个大黑匣子,我感到无望哈哈
这是我为尝试处理登录流程而编写的整个类以供参考:
import Cocoa
import WebKit
class signInWeb: NSViewController, WKNavigationDelegate {
var token = ""
var gotToken = false
static var instance : signInWeb?
var request : URLRequest?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.view.setFrameSize(NSSize.init(width: 1200, height: 900))
let urlString = NSString(format: NSString.init(string : "https://www.facebook.com/v3.3/dialog/oauth?client_id=%@&redirect_uri=%@&response_type=token"), Facebook.AppId, "https://www.facebook.com/connect/login_success.html") as String
let facebookUrl = URL(string: urlString)
var facebookLoginRequest = URLRequest.init(url: facebookUrl!)
self.request = facebookLoginRequest
HTTPCookieStorage.shared.removeCookies(since: .distantPast)
facebookLoginRequest.httpShouldHandleCookies = false
signInWebView.load(facebookLoginRequest)
signInWebView.navigationDelegate = self
let loadedColor = ColorGetter.getCurrentThemeColor()
backButton.font = .labelFont(ofSize: 15)
if loadedColor != ThemeColor.white {
backButton.setText(str: "Back", color: loadedColor)
} else {
backButton.setText(str: "Back", color: .black)
}
signInWeb.instance = self
}
@IBOutlet weak var signInWebView: WKWebView!
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
//HTTPCookieStorage.shared.removeCookies(since: .distantPast)
if let urlStr = navigationAction.request.url?.absoluteString {
let block = urlStr.range(of: "&ret=login&fbapp_pres=0&logger_id=")
let allow = urlStr.range(of: "&hash=")
if (block != nil && allow == nil) {
print("not ok")
print(urlStr)
decisionHandler(.cancel)
} else {
print("should ok")
print(urlStr)
decisionHandler(.allow)
}
let components = urlStr.components(separatedBy: "#access_token=")
if components.count >= 2 {
let secondHalf = components[1]
let paramComponents = secondHalf.components(separatedBy: "&")
let token = paramComponents[0]
self.token = token
gotToken = true
let parent = self.parent as! SignIn
parent.handleCollapse()
}
}
}
@IBAction func goBack(_ sender: HoverButton) {
let parent = self.parent as! SignIn
parent.handleCollapse()
}
@IBOutlet weak var backButton: HoverButton!
func changeButtonColor(color : NSColor) {
if color != ThemeColor.white {
backButton.setText(str: "Back", color: color)
} else {
backButton.setText(str: "Back", color: .black)
}
}
}
解决方案
回答我自己的问题:首先我尝试了 Kubee 的方法,但奇怪的是它没有成功,因为我发现 cookie 没有存储到httpCookieStore
我在初始化时为其配置的确切 WkWebView 中,但仍然存储在WKWebsiteDataStore.default()
. 所以我最终清除了 cookieWKWebsiteDataStore.default()
并且它起作用了。我认为这与我以某种方式错误地组装我的代码有关,但我还不知道为什么。
一开始我尝试编辑configuration.websiteDataStore
IBOutlet的属性,但后来我查了文档才发现配置是一个@NSCopying
var,也就是说我们调用这个属性的时候只得到了值而不是指针。我想也许这就是为什么将配置更改为 a.nonPersistent()
没有成功的原因。然后我完全删除了 IBOutlet 并以WkWebView
编程方式初始化了它,但它仍然没有成功。
我仍然存在的另一个困惑是,由于我在原始问题中包含供参考的 7 个 URL 正在加载,facebook 发送了几个 cookie。cookie 不只是在一个 URL 中发送,而是分布在多个 URL 中,因此我必须在WKWebsiteDataStore.default()
每次加载新请求时清除 cookie。否则它不起作用。不确定这些 cookie 是如何协同工作的。
推荐阅读
- python - 使用 Python 远程执行时,Curl 也不打印
- java - 在节点内使用 xpath 未获取值,但使用文档
- gmail - 在 Play 商店中上传后,Google 登录集成在我的应用程序中不起作用。为什么?
- python-3.x - 如何将数据从大型数据库加载到 pandas 中?
- database - 在 SAS 中映射 SQL Server 逻辑库
- node.js - nodejs中调试包的nodemon问题
- c# - 在c#中旋转图像
- php - 带重音符号的 PHP json_decode() 不起作用
- javascript - 如何获取在 Javascript 中按下的按钮的名称?
- c# - 如何使用多个参数调用测试方法(NUnit)