热门搜索 :
考研考公
您的当前位置:首页正文

APP安全机制(十三)—— 密码工具:提高用户安全性和体验(三)

来源:东饰资讯网

版本记录

版本号 时间
V1.0 2018.12.01 星期六

前言

源码

1. Swift

首先看一下工程结构。

下面看一下sb中的内容

下面看一下代码

1. API.swift
import Foundation

// MARK: - return types
public enum APIResult {
  case success
  case failure(_ error: String?)
}

public enum MotivationalLotteryResult {
  case success(_ motivation: String)
  case failure
}

// MARK: - API access class

public class API {
  // Update this URL definition
  static let baseURL = URL(string: 
  
  static let defaultsKey = "TOKEN-KEY"
  static let defaults = UserDefaults.standard
  
  public static var token: String? {
    get {
      return defaults.string(forKey: API.defaultsKey)
    }
    set {
      defaults.set(newValue, forKey: API.defaultsKey)
    }
  }
  
  // MARK: - Access functions
  
  /// Register a new user and save the returned access token.
  ///
  /// - Parameters:
  ///   - username: desired username
  ///   - password: desired password
  ///   - completion: closure that receives an `APIResult` after the call completes. This is **NOT** called on the main thread.
  public static func register(_ username: String, password: String, completion: @escaping (APIResult) -> Void) {
    struct RegisterData: Encodable {
      let username: String
      let password: String
    }
    guard let baseURL = baseURL else {
        completion(.failure(nil))
        return
    }
    let body = RegisterData(username: username, password: password)
    let url = baseURL.appendingPathComponent("/api/user")
    var registerRequest = URLRequest(url: url)
    registerRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
    registerRequest.httpMethod = "POST"
    do {
        registerRequest.httpBody = try JSONEncoder().encode(body)
    } catch {
        completion(.failure(nil))
        return
    }
    URLSession.shared.dataTask(with: registerRequest) { data, response, _ in
      DispatchQueue.main.async {
        
        guard let jsonData = data else {
          completion(.failure(nil))
          return
        }
        
        guard
          let httpResponse = response as? HTTPURLResponse,
          200..<300 ~= httpResponse.statusCode else {
            do {
              let json = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any]
              if let error = json?["reason"] as? String  {
                completion(.failure(error))
                return
              }
            } catch {}
            completion(.failure(nil))
            return
        }
        
        do {
          let token = try JSONDecoder().decode(Token.self, from: jsonData)
          self.token = token.token
          completion(.success)
        } catch {
          completion(.failure(nil))
        }
      }
    }
    .resume()
  }
  
  /// Login an existing user and save the returned access token.
  ///
  /// - Parameters:
  ///   - username: user's username
  ///   - password: user's password
  ///   - completion: closure that receives an `APIResult` after the call completes. This is **NOT** called on the main thread.
  public static func login(_ username: String, password: String, completion: @escaping (APIResult) -> Void) {
    guard let loginString = "\(username):\(password)"
      .data(using: .utf8)?
      .base64EncodedString()
      else {
        fatalError()
    }
    guard let baseURL = baseURL else {
        completion(.failure(nil))
        return
    }
    let url = baseURL.appendingPathComponent("/api/login")
    var loginRequest = URLRequest(url: url)
    loginRequest.addValue("Basic \(loginString)", forHTTPHeaderField: "Authorization")
    loginRequest.httpMethod = "POST"
    URLSession.shared.dataTask(with: loginRequest) { data, response, _ in
      DispatchQueue.main.async {
        
        guard let jsonData = data else {
          completion(.failure(nil))
          return
        }
        
        guard
          let httpResponse = response as? HTTPURLResponse,
          200..<300 ~= httpResponse.statusCode else {
            do {
              let json = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any]
              if let error = json?["reason"] as? String  {
                completion(.failure(error))
                return
              }
            } catch {}
            completion(.failure(nil))
            return
        }
        
        do {
          let token = try JSONDecoder().decode(Token.self, from: jsonData)
          self.token = token.token
          completion(.success)
        } catch {
          completion(.failure(nil))
        }
      }
    }
    .resume()
  }
  
  /// Log the user out and delete the saved access token.
  public static func logout() {
    guard let baseURL = baseURL, let token = token else {
        return
    }
    let url = baseURL.appendingPathComponent("/api/logout")
    var logoutRequest = URLRequest(url: url)
    logoutRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    logoutRequest.httpMethod = "POST"
    URLSession.shared.dataTask(with: logoutRequest) { _, response, _ in
      DispatchQueue.main.async {
        guard
          let httpResponse = response as? HTTPURLResponse,
          200..<300 ~= httpResponse.statusCode
          else {
            return
        }
        self.token = nil
      }
    }
    .resume()
  }
  
  /// Change the user's password
  ///
  /// - Parameters:
  ///   - newPassword: the desired new password
  ///   - completion: closure that receives an `APIResult` after the call completes. This is **NOT** called on the main thread.
  public static func changePassword(_ newPassword: String, completion: @escaping (APIResult) -> Void) {
    struct NewPasswordData: Encodable {
      let newPassword: String
    }
    guard let baseURL = baseURL, let token = token else {
        completion(.failure(nil))
        return
    }
    let body = NewPasswordData(newPassword: newPassword)
    let url = baseURL.appendingPathComponent("/api/user")
    var changeRequest = URLRequest(url: url)
    changeRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
    changeRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    changeRequest.httpMethod = "PUT"
    do {
        changeRequest.httpBody = try JSONEncoder().encode(body)
    } catch {
        completion(.failure(nil))
        return
    }
    URLSession.shared.dataTask(with: changeRequest) { _, response, _ in
      DispatchQueue.main.async {
        guard
          let httpResponse = response as? HTTPURLResponse,
          200..<300 ~= httpResponse.statusCode
          else {
            completion(.failure(nil))
            return
        }
        
        completion(.success)
      }
    }
    .resume()
  }
  
  /// Retreive a new motivational quote for the logged in user
  ///
  /// - Parameter completion: closure that receives a `MotivationalLotteryResult` after the call completes. This is **NOT** called on the main thread.
  public static func motivationalLottery(_ completion: @escaping (MotivationalLotteryResult) -> Void) {
    struct MotivationalLotteryData: Decodable {
      let motivation: String
    }
    guard let baseURL = baseURL, let token = token else {
        completion(.failure)
        return
    }
    let url = baseURL.appendingPathComponent("/api/motivation")
    var motivationalLotteryRequest = URLRequest(url: url)
    motivationalLotteryRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
    motivationalLotteryRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    URLSession.shared.dataTask(with: motivationalLotteryRequest) {
      data, response, _ in
      DispatchQueue.main.async {
        guard
          let httpResponse = response as? HTTPURLResponse,
          200..<300 ~= httpResponse.statusCode,
          let jsonData = data
          else {
            completion(.failure)
            return
        }
        do {
          let motivationalLottery = try JSONDecoder().decode(MotivationalLotteryData.self, from: jsonData)
          completion(.success(motivationalLottery.motivation))
        } catch {
          completion(.failure)
        }
      }
    }
    .resume()
  }
}

// MARK: - private structure

final class Token: Codable {
  var token: String
  var userID: UUID
  
  init(token: String, userID: UUID) {
    self.token = token
    self.userID = userID
  }
}
2. UltraMotivatorViewController.swift
import UIKit

class UltraMotivatorViewController: UIViewController {
  var keyboardDismisser : UITapGestureRecognizer!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    keyboardDismisser = UITapGestureRecognizer(target: self, action:#selector(handleTap(_:)))
    view.addGestureRecognizer(keyboardDismisser)
  }
  
  @objc func handleTap(_ recognizer: UITapGestureRecognizer) {
    view.endEditing(true)
  }
  
  func fillInFieldsReminder() {
    showAlert("Error", message: "Fill in all the fields, please.")
  }
  
  func showAlert(_ title: String, message: String? = nil, completion: (() -> Void)? = nil) {
    let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
    let OKAction = UIAlertAction(title: "OK", style: .default) { _ in completion?() }
    alertController.addAction(OKAction)
    present(alertController, animated: true)
  }
}
3. LoginViewController.swift
import UIKit

class LoginViewController: UltraMotivatorViewController {
  @IBOutlet var usernameField : UITextField!
  @IBOutlet var passwordField : UITextField!
  @IBOutlet var submitButton : UIButton!
  
  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(true)
    view.endEditing(true)
    usernameField.text = nil
    passwordField.text = nil
  }
  
  @IBAction private func signIn(_ sender: Any) {
    view.endEditing(true)
    
    guard
      let username = usernameField.text,
      let password = passwordField.text,
      !username.isEmpty, !password.isEmpty
      else {
        fillInFieldsReminder()
        return
    }

    submitButton.isEnabled = false
    
    API.login(username, password: password) { result in
      self.submitButton.isEnabled = true
      switch result {
      case .success:
        self.performSegue(withIdentifier: "Logged In", sender: nil)
      case .failure(let error):
        self.showAlert("Error", message: error ?? "Login Failed")
      }
    }
  }
}
4. SignupViewController.swift
import UIKit

class SignupViewController: UltraMotivatorViewController {
  @IBOutlet var usernameField : UITextField!
  @IBOutlet var passwordField : UITextField!
  @IBOutlet var submitButton : UIButton!
  
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    usernameField.becomeFirstResponder()
  }
  
  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(true)
    view.endEditing(true)
    if API.token == nil {
        usernameField.text = nil
        passwordField.text = nil
    } else {
        API.logout()
    }
  }

  @IBAction func signUp(_ sender: Any) {
    view.endEditing(true)
    
    guard
      let username = usernameField.text,
      let password = passwordField.text,
      !username.isEmpty, !password.isEmpty
      else {
        fillInFieldsReminder()
        return
    }

    submitButton.isEnabled = false
    
    API.register(username, password: password) { result in
      self.submitButton.isEnabled = true
      switch result {
      case .success:
        self.showAlert("Signed Up!", message: "Log in to get motivated") {
            self.navigationController?.popToRootViewController(animated: true)
        }
      case .failure(let error):
        self.showAlert("Error", message: error ?? "Sign Up Failed")
      }
    }
  }
}
5. OneTimeCodeViewController.swift
import UIKit

class OneTimeCodeViewController: UltraMotivatorViewController {
  @IBOutlet weak var oneTimeCodeField: UITextField!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.setHidesBackButton(true, animated: false)
  }
  
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
        oneTimeCodeField.textContentType = .oneTimeCode
    oneTimeCodeField.becomeFirstResponder()
  }
  
  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(true)
    view.endEditing(true)
    oneTimeCodeField.text = nil
  }
  
  @IBAction func submitTapped(_ sender: Any) {
    view.endEditing(true)
    performSegue(withIdentifier: "Verified", sender: nil)
  }
}
6. MotivationalViewController.swift
import UIKit

class MotivationalViewController: UIViewController {
  @IBOutlet weak var motivationalLabel: UILabel!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.setHidesBackButton(true, animated: false)
  }
  
  override func viewWillAppear(_ animated: Bool) {
    motivationalLabel.isHidden = true
    API.motivationalLottery { result in
      if case let .success(motivation) = result {
        self.motivationalLabel.text = motivation
      }
      self.motivationalLabel.isHidden = false
    }
  }
  
  @IBAction func logoutTapped(_ sender: Any) {
    API.logout()
    navigationController?.popToRootViewController(animated: true)
  }
}

后记

本篇讲述了提高用户安全性和体验,感兴趣的给个赞或者关注~~~

Top