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

Swift 4 WKWebView Authentication From Keychain

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

我正在使用 Swift 4 和 WKWebView。当用户提交登录表单时,有没有一种方法可以将用户的凭据存储在特定 URL 的钥匙串中?我发现我可以使用 Javascript 来使用 WKWebView here but that's using swift 2 or 1Password. I also found this post 来操作表单字段,展示如何使用钥匙串来存储凭据。有点在寻找两者的结合。我还需要知道我需要启用哪些 plist 才能使其正常工作。

我要完成的步骤:

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

  2. CakePHP 发现用户未被授权并重定向到登录页面,GET "/users/login".

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

  4. 在发布表单之前,请求将凭据存储在钥匙串中的权限。

  5. 用户关闭应用程序,清除 session。

  6. 用户打开应用程序,重复第 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: 正如评论中所建议的,我通过检查请求 header 并设置 session 添加了基于令牌的身份验证功能令牌存在。当用户第一次登录时,将生成令牌并在响应中发回。我在 ViewController 中添加了另一个 webView 函数以在响应 header.

中查找令牌
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: 事实证明,在 URL 请求 object 中添加 header 在 Swift 中并不难].我已经更新了我的 viewDidLoad() 以在 webView 加载请求之前添加令牌 header 。我将 URL 更改为直接进入登录页面。我已经对此进行了测试,我的用户已获得授权并在 header 存在时设置了他的 session。

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: 使用此 ,我更新了我的 ViewController 以存储在响应中找到的令牌并将令牌添加到header 在 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)
}

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

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

WKNavigationDelegate has a method 传入 WKNavigationResponse object,它允许您查看响应,包括 headers。

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 class 可用于存储、检索或删除令牌。

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)

要仅将 header 添加到您的初始请求,您可以修改 URLRequest object 以在 webView 加载它之前添加 header。

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)
}