在 Swift 中使用基于令牌的身份验证(多个视图控制器)处理用户登录
Handle user login with token base authentication (multiple ViewControllers) in Swift
我正在处理一个应用程序,但在登录和移动到主 ViewController 时遇到问题。
目标: 显示登录ViewController 如果用户尚未登录,一旦登录,转至 CannabisListViewController.
现在,当我 select 模拟器中的登录按钮时,令牌和令牌的到期日期会在 header 中打印出来。我还验证了后端生成了一个新令牌。
API 响应
Server WSGIServer/0.2 CPython/3.7.2
Set-Cookie csrftoken=XelHDMTehmhJxLe5q6uqDYCymZV1iUaKNzRqtOcYYrmMvNkKTnuRbkjHEM2LR8Xo; expires=Fri, 26 Feb 2021 03:58:03 GMT; Max-Age=31449600; Path=/; SameSite=Lax, sessionid=5cu84ccaquq46ga8kazfjequ136y24g1; expires=Fri, 13 Mar 2020 03:58:03 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
Date Fri, 28 Feb 2020 03:58:03 GMT
Content-Type application/json
Content-Length 50
Vary Accept, Cookie
X-Frame-Options SAMEORIGIN
Allow POST, OPTIONS
后端:
Django/Django Rest 框架和 Django Rest Auth
我目前所在的位置:
我还使用 UserDefaults 来存储我认为是令牌的内容,但我不确定我是否正确实施它或如何存储令牌以便我可以检查它是否存在并更改基于 ViewController
场景代理
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// IF USER IS LOGGED IN
if let _ = userToken?.key {
// CREATE TAB BAR //
let tabController = UITabBarController()
// Instantiate the storyboards
let cannabisStoryboard = UIStoryboard(name: "Cannabis", bundle: nil)
let profileStoryboard = UIStoryboard(name: "Profile", bundle: nil)
// Instantiate the view controllers to storyboards
let cannabisVC = cannabisStoryboard.instantiateViewController(withIdentifier: "Cannabis") as! CannabisViewController
let profileVC = profileStoryboard.instantiateViewController(withIdentifier: "Profile") as! ProfileViewController
// Displays the items in below order in tab bar
let vcData: [(UIViewController, UIImage, UIImage)] = [
(cannabisVC, UIImage(named: "Cannabis_icon")!, UIImage(named: "Cannabis_icon_selected")!),
(profileVC, UIImage(named: "Profile_icon")!, UIImage(named: "Profile_icon_selected")!),
]
let vcs = vcData.map { (vc, defaultImage, selectedImage) -> UINavigationController in
let nav = UINavigationController(rootViewController: vc)
nav.tabBarItem.image = defaultImage
nav.tabBarItem.selectedImage = selectedImage
return nav
}
tabController.viewControllers = vcs
tabController.tabBar.isTranslucent = false
tabController.delegate = tabBarDelegate
// Disables rendering for tab bar images
if let items = tabController.tabBar.items {
for item in items {
if let image = item.image {
item.image = image.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
if let selectedImage = item.selectedImage {
item.selectedImage = selectedImage.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
// Hides title
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
// Customize Navigation bar
UINavigationBar.appearance().backgroundColor = UIColor(rgb: 0x00ffcc)
//
window?.rootViewController = tabController
self.window?.makeKeyAndVisible()
// CREATE TAB BAR //
} else {
let loginStoryboard = UIStoryboard(name: "Login", bundle: nil)
let loginViewController = loginStoryboard.instantiateViewController(withIdentifier: "Login") as! LoginViewController
window?.rootViewController = loginViewController
}
guard let _ = (scene as? UIWindowScene) else { return }
登录ViewController
import UIKit
class LoginViewController: UIViewController {
// MARK: - Outlets
@IBOutlet var usernameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
@IBOutlet var loginButton: UIButton!
// MARK: - Properties
let rest = APIClient()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// MARK: - Actions
@IBAction func loginButtonDidTouch(_ sender: Any) {
guard let username = usernameTextField.text else { return }
guard let password = passwordTextField.text else { return }
// MARK: URL
guard let url = URL(string: APIClient.shared.loginURL) else { return }
// rest.requestHttpHeaders.add(value: "application/x-www-form-urlencoded", forKey: "Content-Type")
rest.requestHttpHeaders.add(value: "application/json", forKey: "Content-Type")
rest.httpBodyParameters.add(value: "\(username)", forKey: "username")
rest.httpBodyParameters.add(value: "\(password)", forKey: "password")
rest.makeRequest(toURL: url, withHttpMethod: .post) { (results) in
// MARK: Response
print("\n\n### Response HTTP Headers ###\n")
if let response = results.response {
for (key, value) in response.headers.allValues() {
print(key, value)
}
}
print("\n\n### End Response HTTP Headers ###\n")
// MARK: Data
if let data = results.data {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
guard let item = try? decoder.decode(CustomUser.self, from: data) else { return }
print(item.key)
let userAuthToken = item.key
UserDefaults.standard.set(userAuthToken, forKey: "key")
Helper.login()
} else {
print("Unable to serialize data")
}
DispatchQueue.main.async {
let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
spinner.frame = CGRect(x: 0, y: 0, width: 5, height: 44)
}
}
print(username)
print(password)
}
}
Helper Function(这基本上是场景委托,但我计划用它来让用户在配置文件中进出登录ViewController)
Login/Out 函数(我也收到了 TabBarDelegate 的弱引用警告,如果有人想要额外的挑战)
class Helper {
class func login() {
let tabController = UITabBarController()
// Instantiate the storyboards
let cannabisStoryboard = UIStoryboard(name: "Cannabis", bundle: nil)
let profileStoryboard = UIStoryboard(name: "Profile", bundle: nil)
// Instantiate the view controllers to storyboards
let cannabisVC = cannabisStoryboard.instantiateViewController(withIdentifier: "Cannabis") as! CannabisViewController
let profileVC = profileStoryboard.instantiateViewController(withIdentifier: "Profile") as! ProfileViewController
// Displays the items in below order in tab bar
let vcData: [(UIViewController, UIImage, UIImage)] = [
(cannabisVC, UIImage(named: "Cannabis_icon")!, UIImage(named: "Cannabis_icon_selected")!),
(profileVC, UIImage(named: "Profile_icon")!, UIImage(named: "Profile_icon_selected")!),
]
let vcs = vcData.map { (vc, defaultImage, selectedImage) -> UINavigationController in
let nav = UINavigationController(rootViewController: vc)
nav.tabBarItem.image = defaultImage
nav.tabBarItem.selectedImage = selectedImage
return nav
}
tabController.viewControllers = vcs
tabController.tabBar.isTranslucent = false
tabController.delegate = TabBarDelegate() (Weak reference warning here)
// Disables rendering for tab bar images
if let items = tabController.tabBar.items {
for item in items {
if let image = item.image {
item.image = image.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
if let selectedImage = item.selectedImage {
item.selectedImage = selectedImage.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
// Hides title
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
// Customize Navigation bar
UINavigationBar.appearance().backgroundColor = UIColor(rgb: 0x00ffcc)
//
let appDelegate = UIApplication.shared.delegate as! AppDelegate
guard let window = appDelegate.window else { return }
window.rootViewController = tabController
}
class func logout() {
let loginStoryboard = UIStoryboard(name: "Login", bundle: nil)
let loginViewController = loginStoryboard.instantiateViewController(withIdentifier: "Login") as! LoginViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
guard let window = appDelegate.window else { return }
window.rootViewController = loginViewController
}
}
用户模型
struct CustomUser: Codable {
static var current: CustomUser!
var id: Int
var username: String
var password: String
var email: String
var photo: URL
var token: String
var key: String
}
会发生什么
如前所述,当我 select LoginViewController 中的 Login 按钮时,令牌信息被打印出来,但登录屏幕从未移动到应用程序的主要部分。我假设错误是在将令牌存储在 UserDelegate 中并验证它确实存在的某个地方,但我可能是错的。
我尝试搜索 Google 但我主要是在 Auth0 上找到教程(我计划在更好地理解我现在正在尝试做的事情之后稍后实施)和 FireBase。
如果有更好的方法来实现我想做的事情,我也愿意尝试一下。如果您有我可以查看的教程参考(并保存以备后用),请分享!
提前致谢!
guard let window = appDelegate.window else { return }
这将 return 因为 appDelegate.window 为零。
确保您的代码在 SceneDelegate 的正确 window 上设置 UITabbarController。
我正在处理一个应用程序,但在登录和移动到主 ViewController 时遇到问题。
目标: 显示登录ViewController 如果用户尚未登录,一旦登录,转至 CannabisListViewController.
现在,当我 select 模拟器中的登录按钮时,令牌和令牌的到期日期会在 header 中打印出来。我还验证了后端生成了一个新令牌。
API 响应
Server WSGIServer/0.2 CPython/3.7.2
Set-Cookie csrftoken=XelHDMTehmhJxLe5q6uqDYCymZV1iUaKNzRqtOcYYrmMvNkKTnuRbkjHEM2LR8Xo; expires=Fri, 26 Feb 2021 03:58:03 GMT; Max-Age=31449600; Path=/; SameSite=Lax, sessionid=5cu84ccaquq46ga8kazfjequ136y24g1; expires=Fri, 13 Mar 2020 03:58:03 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
Date Fri, 28 Feb 2020 03:58:03 GMT
Content-Type application/json
Content-Length 50
Vary Accept, Cookie
X-Frame-Options SAMEORIGIN
Allow POST, OPTIONS
后端: Django/Django Rest 框架和 Django Rest Auth
我目前所在的位置: 我还使用 UserDefaults 来存储我认为是令牌的内容,但我不确定我是否正确实施它或如何存储令牌以便我可以检查它是否存在并更改基于 ViewController
场景代理
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// IF USER IS LOGGED IN
if let _ = userToken?.key {
// CREATE TAB BAR //
let tabController = UITabBarController()
// Instantiate the storyboards
let cannabisStoryboard = UIStoryboard(name: "Cannabis", bundle: nil)
let profileStoryboard = UIStoryboard(name: "Profile", bundle: nil)
// Instantiate the view controllers to storyboards
let cannabisVC = cannabisStoryboard.instantiateViewController(withIdentifier: "Cannabis") as! CannabisViewController
let profileVC = profileStoryboard.instantiateViewController(withIdentifier: "Profile") as! ProfileViewController
// Displays the items in below order in tab bar
let vcData: [(UIViewController, UIImage, UIImage)] = [
(cannabisVC, UIImage(named: "Cannabis_icon")!, UIImage(named: "Cannabis_icon_selected")!),
(profileVC, UIImage(named: "Profile_icon")!, UIImage(named: "Profile_icon_selected")!),
]
let vcs = vcData.map { (vc, defaultImage, selectedImage) -> UINavigationController in
let nav = UINavigationController(rootViewController: vc)
nav.tabBarItem.image = defaultImage
nav.tabBarItem.selectedImage = selectedImage
return nav
}
tabController.viewControllers = vcs
tabController.tabBar.isTranslucent = false
tabController.delegate = tabBarDelegate
// Disables rendering for tab bar images
if let items = tabController.tabBar.items {
for item in items {
if let image = item.image {
item.image = image.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
if let selectedImage = item.selectedImage {
item.selectedImage = selectedImage.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
// Hides title
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
// Customize Navigation bar
UINavigationBar.appearance().backgroundColor = UIColor(rgb: 0x00ffcc)
//
window?.rootViewController = tabController
self.window?.makeKeyAndVisible()
// CREATE TAB BAR //
} else {
let loginStoryboard = UIStoryboard(name: "Login", bundle: nil)
let loginViewController = loginStoryboard.instantiateViewController(withIdentifier: "Login") as! LoginViewController
window?.rootViewController = loginViewController
}
guard let _ = (scene as? UIWindowScene) else { return }
登录ViewController
import UIKit
class LoginViewController: UIViewController {
// MARK: - Outlets
@IBOutlet var usernameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
@IBOutlet var loginButton: UIButton!
// MARK: - Properties
let rest = APIClient()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// MARK: - Actions
@IBAction func loginButtonDidTouch(_ sender: Any) {
guard let username = usernameTextField.text else { return }
guard let password = passwordTextField.text else { return }
// MARK: URL
guard let url = URL(string: APIClient.shared.loginURL) else { return }
// rest.requestHttpHeaders.add(value: "application/x-www-form-urlencoded", forKey: "Content-Type")
rest.requestHttpHeaders.add(value: "application/json", forKey: "Content-Type")
rest.httpBodyParameters.add(value: "\(username)", forKey: "username")
rest.httpBodyParameters.add(value: "\(password)", forKey: "password")
rest.makeRequest(toURL: url, withHttpMethod: .post) { (results) in
// MARK: Response
print("\n\n### Response HTTP Headers ###\n")
if let response = results.response {
for (key, value) in response.headers.allValues() {
print(key, value)
}
}
print("\n\n### End Response HTTP Headers ###\n")
// MARK: Data
if let data = results.data {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
guard let item = try? decoder.decode(CustomUser.self, from: data) else { return }
print(item.key)
let userAuthToken = item.key
UserDefaults.standard.set(userAuthToken, forKey: "key")
Helper.login()
} else {
print("Unable to serialize data")
}
DispatchQueue.main.async {
let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
spinner.frame = CGRect(x: 0, y: 0, width: 5, height: 44)
}
}
print(username)
print(password)
}
}
Helper Function(这基本上是场景委托,但我计划用它来让用户在配置文件中进出登录ViewController)
Login/Out 函数(我也收到了 TabBarDelegate 的弱引用警告,如果有人想要额外的挑战)
class Helper {
class func login() {
let tabController = UITabBarController()
// Instantiate the storyboards
let cannabisStoryboard = UIStoryboard(name: "Cannabis", bundle: nil)
let profileStoryboard = UIStoryboard(name: "Profile", bundle: nil)
// Instantiate the view controllers to storyboards
let cannabisVC = cannabisStoryboard.instantiateViewController(withIdentifier: "Cannabis") as! CannabisViewController
let profileVC = profileStoryboard.instantiateViewController(withIdentifier: "Profile") as! ProfileViewController
// Displays the items in below order in tab bar
let vcData: [(UIViewController, UIImage, UIImage)] = [
(cannabisVC, UIImage(named: "Cannabis_icon")!, UIImage(named: "Cannabis_icon_selected")!),
(profileVC, UIImage(named: "Profile_icon")!, UIImage(named: "Profile_icon_selected")!),
]
let vcs = vcData.map { (vc, defaultImage, selectedImage) -> UINavigationController in
let nav = UINavigationController(rootViewController: vc)
nav.tabBarItem.image = defaultImage
nav.tabBarItem.selectedImage = selectedImage
return nav
}
tabController.viewControllers = vcs
tabController.tabBar.isTranslucent = false
tabController.delegate = TabBarDelegate() (Weak reference warning here)
// Disables rendering for tab bar images
if let items = tabController.tabBar.items {
for item in items {
if let image = item.image {
item.image = image.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
if let selectedImage = item.selectedImage {
item.selectedImage = selectedImage.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
}
// Hides title
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
// Customize Navigation bar
UINavigationBar.appearance().backgroundColor = UIColor(rgb: 0x00ffcc)
//
let appDelegate = UIApplication.shared.delegate as! AppDelegate
guard let window = appDelegate.window else { return }
window.rootViewController = tabController
}
class func logout() {
let loginStoryboard = UIStoryboard(name: "Login", bundle: nil)
let loginViewController = loginStoryboard.instantiateViewController(withIdentifier: "Login") as! LoginViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
guard let window = appDelegate.window else { return }
window.rootViewController = loginViewController
}
}
用户模型
struct CustomUser: Codable {
static var current: CustomUser!
var id: Int
var username: String
var password: String
var email: String
var photo: URL
var token: String
var key: String
}
会发生什么 如前所述,当我 select LoginViewController 中的 Login 按钮时,令牌信息被打印出来,但登录屏幕从未移动到应用程序的主要部分。我假设错误是在将令牌存储在 UserDelegate 中并验证它确实存在的某个地方,但我可能是错的。
我尝试搜索 Google 但我主要是在 Auth0 上找到教程(我计划在更好地理解我现在正在尝试做的事情之后稍后实施)和 FireBase。
如果有更好的方法来实现我想做的事情,我也愿意尝试一下。如果您有我可以查看的教程参考(并保存以备后用),请分享!
提前致谢!
guard let window = appDelegate.window else { return }
这将 return 因为 appDelegate.window 为零。
确保您的代码在 SceneDelegate 的正确 window 上设置 UITabbarController。