首页 > 解决方案 > 来自钥匙串的 Swift 4 WKWebView 身份验证

问题描述

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

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

我要完成的步骤:

  1. 用户第一次打开应用,当 WKWebView 导航到主 URL 时,获取“/”。

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

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

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

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

  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:正如评论中所建议的,我通过检查请求标头并在令牌存在时设置会话来添加基于令牌的身份验证功能。当用户第一次登录时,会生成令牌并在响应中发回。我在 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:使用这个stackoverflow 答案,我更新了我的 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)
}

我愿意接受有关如何使其变得更好的建议,但目前它符合预期。

标签: authenticationswift4ios11cakephp-3.x

解决方案


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

WKNavigationDelegate有一个传入 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)
}

推荐阅读