Swift 在macOS应用程序中,使用Wkwebview的Facebook手动登录流失败
Swift 在macOS应用程序中,使用Wkwebview的Facebook手动登录流失败,swift,cocoa,facebook-login,wkwebview,httpcookie,Swift,Cocoa,Facebook Login,Wkwebview,Httpcookie,WKWebView没有按要求执行“从Facebook登录”任务,因为浏览器似乎总是记住以前用于登录的帐户凭据,即使我在发出登录请求之前和之后都试图清除cookie 我正在为MacOS项目手动构建Facebook登录流,遵循以下指南: 我决定使用嵌入式web浏览器(WKWebView)来处理登录。我想要实现的是,当用户单击“从Facebook登录”按钮时,WKWebView将显示为一张表单,加载请求,显示对话框,让用户输入他的电子邮件和密码,并且在用户成功登录并获得带令牌的重定向uri后,关闭自
WKWebView
没有按要求执行“从Facebook登录”任务,因为浏览器似乎总是记住以前用于登录的帐户凭据,即使我在发出登录请求之前和之后都试图清除cookie
我正在为MacOS项目手动构建Facebook登录流,遵循以下指南:
我决定使用嵌入式web浏览器(WKWebView
)来处理登录。我想要实现的是,当用户单击“从Facebook登录”按钮时,WKWebView
将显示为一张表单,加载请求,显示对话框,让用户输入他的电子邮件和密码,并且在用户成功登录并获得带令牌的重定向uri
后,关闭自身以完成登录流
这是第一次在新计算机上执行此过程,它工作正常。但是,一旦我作为一个用户成功登录,浏览器似乎会自动记住帐户凭据,下次我单击“从Facebook登录”时,它会显示,然后立即关闭,这意味着它会直接跳转到重定向uri,并将前一个用户的令牌附加到uri
我对程序如何处理缓存或如何存储用户凭据没有太多的先验知识,但我认为它与cookie有关。所以我做了
HTTPCookieStorage.shared.removeCookies(since: .distantPast)
及
在我认为合适的地方
这些行动起了作用,但仍然不能解决问题。浏览器不再“在显示后”立即关闭自己,但仍然可能在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是当浏览器中断您、自动将您记录为前一个用户并自行关闭时加载的内容。它并不总是发生,或者至少它不总是在相同的时间之后发生,所以它对我来说是相当不可预测的。我想这可能和清理饼干有关,但我不知道怎么做
如果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)
}
}
}
如果要擦除所有Cookie,可以尝试以下操作:
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
如果您只想擦除会话cookie,可以尝试以下操作:
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
编辑:
我让一个人将我的示例降级,但没有解释原因。但也许我是在误导。第二个示例通常不起任何作用,因为默认情况下,每个web视图都有自己的进程池,除非您在另一个web视图已打开时实例化一个web视图(不知何故,apple将重新使用相同的进程池)。然而,它揭示的是,您可以在不同的web视图之间共享一个进程池,以便在它们之间共享会话cookie(与清除会话cookie相反),并且一旦您想要清除会话cookie,您就可以销毁该进程池并创建一个新的进程池。因此,如果您想在不同的web视图之间共享会话cookie,只需执行以下操作:
extension WKProcessPool {
static var shared = WKProcessPool()
}
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool.shared
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
要清除会话cookies,您需要执行以下操作:
WKProcessPool.shared = WKProcessPool()
现在,下次您通过上面的懒惰示例创建webView时,您将拥有一个全新的进程池,您的会话Cookie将被擦除。如果您想擦除所有Cookie,您可以尝试以下操作:
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool()
return WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
}()
如果您只想擦除会话cookie,可以尝试以下操作: