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 才能使其正常工作。
我要完成的步骤:
用户第一次打开应用,当WKWebView导航到主界面时URL,GET "/".
CakePHP 发现用户未被授权并重定向到登录页面,GET "/users/login".
此时,我想检查钥匙串中与此 URL 相关的凭据,但由于这是用户第一次打开应用程序,凭据不应该存在。用户登录并点击提交,POST "/users/login".
在发布表单之前,请求将凭据存储在钥匙串中的权限。
用户关闭应用程序,清除 session。
用户打开应用程序,重复第 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)
}
我有一个使用 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 才能使其正常工作。
我要完成的步骤:
用户第一次打开应用,当WKWebView导航到主界面时URL,GET "/".
CakePHP 发现用户未被授权并重定向到登录页面,GET "/users/login".
此时,我想检查钥匙串中与此 URL 相关的凭据,但由于这是用户第一次打开应用程序,凭据不应该存在。用户登录并点击提交,POST "/users/login".
在发布表单之前,请求将凭据存储在钥匙串中的权限。
用户关闭应用程序,清除 session。
用户打开应用程序,重复第 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: 使用此
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)
}