来自钥匙串的 Swift 4 WKWebView 身份验证

Posted

技术标签:

【中文标题】来自钥匙串的 Swift 4 WKWebView 身份验证【英文标题】:Swift 4 WKWebView Authentication From Keychain 【发布时间】:2018-08-27 17:29:33 【问题描述】:

我有一个使用 Cakephp 3.x 的非常简单的网页,我编写了一个简单的 Swift 4 应用程序来显示该网站。只要应用程序保持打开状态,会话就会继续工作。应用程序关闭后,用户必须再次登录。我相信这是正确的行为,因为会话 cookie 存储在内存中,并在浏览器关闭后被删除,但如果我错了,请纠正我。我的用户希望登录一次,每次重新打开应用时都会自动登录。

我正在使用 Swift 4 和 WKWebView。当用户提交登录表单时,有没有办法可以将用户的凭据存储在特定 URL 的钥匙串中?我发现我可以使用 javascript 来使用 WKWebView here 操作表单字段,但这是使用 swift 2 或 1Password。我还发现this post 展示了如何使用钥匙串来存储凭据。有点寻找两者的结合。我还需要知道我需要启用哪些 plist 才能使其正常工作。

我正在尝试完成的步骤:

    用户第一次打开应用,当 WKWebView 导航到主 URL 时,GET "/"。

    CakePHP 看到用户没有被授权并重定向到登录页面,GET "/users/login"。

    此时,我想检查钥匙串中与此 URL 相关的凭据,但由于这是用户第一次打开应用程序,因此凭据不应该存在。用户登录并点击提交,POST“/users/login”。

    在发布表单之前,请求允许在钥匙串中存储凭据。

    用户关闭应用,清除会话。

    用户打开应用程序,重复第 1 步和第 2 步,但这次第 3 步的凭据确实存在。此时我想从钥匙串中加载凭据,填写用户名和密码字段,然后触发提交。

这是我的视图控制器的副本:

import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate 
    var webView: WKWebView!

    override func viewDidLoad() 
        super.viewDidLoad()
        let url = URL(string: "https://www.example.com")!
        webView.load(URLRequest(url:url))
    

    override func didReceiveMemoryWarning() 
        super.didReceiveMemoryWarning()
    

    override func loadView() 
        webView = WKWebView()
        webView.navigationDelegate = self
        view = webView
    

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation) 
        title = webView.title
    

编辑 1: 正如 cmets 中所建议的,我通过检查请求标头并在令牌存在时设置会话来添加基于令牌的身份验证功能。当用户第一次登录时,会生成令牌并在响应中发回。我在 ViewController 中添加了另一个 webView 函数来在响应标头中查找令牌。

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) 
    let token = (navigationResponse.response as! HTTPURLResponse).allHeaderFields["X-SAMPLE-TOKEN-HEADER"] as? String
    print(token)
    decisionHandler(.allow)
 

编辑 2: 事实证明,在 Swift 中向 URLRequest 对象添加标头并不难。我已经更新了我的 viewDidLoad() 以在 webView 加载请求之前添加令牌标头。我更改了 URL 以直接进入登录页面。我已经对此进行了测试,并且当标头存在时,我的用户已获得授权并设置了他的会话。

override func viewDidLoad() 
    super.viewDidLoad()      
    var request = URLRequest(url:URL(string: "https://www.example.com/users/login")!)
    request.addValue("abcdef01234567890fedcba9871634556211", forHTTPHeaderField: "X-SAMPLE-TOKEN-HEADER")
    webView.load(request)

编辑 3: 使用这个*** answer,我更新了我的 ViewController 以存储在响应中找到的令牌,并在 webView 加载 URLRequest 之前将令牌添加到标头中。

let key = "com.example.www.token"
let header = "X-SAMPLE-TOKEN-HEADER"

override func viewDidLoad() 
    super.viewDidLoad()
    var request = URLRequest(url:URL(string: "https://www.example.com/users/login")!)
    let token = UserDefaults.standard.string(forKey: key)

    if (token != nil) 
        request.addValue(token!, forHTTPHeaderField: header)
    

    webView.load(request)


func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) 

    let token = (navigationResponse.response as! HTTPURLResponse).allHeaderFields[header] as? String

    if (token != nil) 
        if (UserDefaults.standard.string(forKey: key) != nil) 
            UserDefaults.standard.removeObject(forKey: key)
        

        UserDefaults.standard.set(token, forKey: key)
    

    decisionHandler(.allow)

我愿意接受有关如何改进它的建议,但目前它符合预期。

【问题讨论】:

会话(在本例中为 PHP)是短暂的,与 cookie 相同。最好创建一个令牌系统来保存您的登录信息(IE:登录时生成一个令牌并将其保存到与用户关联的服务器端数据存储中)然后将令牌保存在手机上(Keychain/NSUserDefaults)然后,在您的UIWebview 请求,使用 NSMutableURLRequest 添加带有令牌的标头以在服务器上进行身份验证。 @John 谢谢,我对后端进行了更改以在登录时生成令牌,并在标题中提供令牌。我在应用程序的 ViewController 中添加了一个函数来查找 navigationResponse 中的标题。我会看看你接下来建议的类,以了解如何将令牌存储在钥匙串中。 【参考方案1】:

按照建议使用基于令牌的身份验证,在用户登录时生成令牌并在响应标头中传回。

WKNavigationDelegate 有一个 method 传入 WKNavigationResponse 对象,它允许您查看响应,包括标头。

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) 
    ... do stuff

    let headerValue = (navigationResponse.response as! HTTPURLResponse).allHeaderFields["X-HEADER-NAME"] as? String

    ... do more stuff

UserDefaults 类可用于存储、检索或删除令牌。

let tokenKey = "unique.identifier"
let newToken = "NEWTOKEN12345"
let oldToken = UserDefaults.standard.string(forKey: tokenKey)

if (oldToken != nil) 
    UserDefaults.standard.removeObject(forKey: tokenKey)


UserDefaults.standard.set(token, forKey: tokenKey)

要仅为初始请求添加标头,您可以修改 URLRequest 对象以在 webView 加载标头之前添加标头。

override func viewDidLoad() 
    ... do stuff

    let tokenKey = "unique.identifier"
    let token = UserDefaults.standard.string(forKey: tokenKey)

    var request = URLRequest(url:URL(string: "https://www.example.com/users/login")!)

    if (token != nil) 
        request.addValue(token!, forHTTPHeaderField: header)
    

    ... do more stuff

    webView.load(request)

【讨论】:

以上是关于来自钥匙串的 Swift 4 WKWebView 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

使用 Swift 4 滚动 WKWebview

如何在 C#/Xamarin 中使用带有来自 android 钥匙串的私钥的 X.509 证书?

如何在 Swift-Wkwebview 上自动填写用户名和密码?

swift [初学者来自iOS中WKWebView的简单浏览器] Chp6的青铜挑战程序设计查看Big Nerd Ranch #tags:swift3,ios,UIView,WKWebview

通过 TOR swift 路由 WKWebView

Swift 4/5 设置将代理添加到 WKWebview