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

2018-12-01  本文已影响52人  刀客传奇

版本记录

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

前言

在这个信息爆炸的年代,特别是一些敏感的行业,比如金融业和银行卡相关等等,这都对app的安全机制有更高的需求,很多大公司都有安全 部门,用于检测自己产品的安全性,但是及时是这样,安全问题仍然被不断曝出,接下来几篇我们主要说一下app的安全机制。感兴趣的看我上面几篇。
1. APP安全机制(一)—— 几种和安全性有关的情况
2. APP安全机制(二)—— 使用Reveal查看任意APP的UI
3. APP安全机制(三)—— Base64加密
4. APP安全机制(四)—— MD5加密
5. APP安全机制(五)—— 对称加密
6. APP安全机制(六)—— 非对称加密
7. APP安全机制(七)—— SHA加密
8. APP安全机制(八)—— 偏好设置的加密存储
9. APP安全机制(九)—— 基本iOS安全之钥匙链和哈希(一)
10. APP安全机制(十)—— 基本iOS安全之钥匙链和哈希(二)
11. APP安全机制(十一)—— 密码工具:提高用户安全性和体验(一)
12. APP安全机制(十二)—— 密码工具:提高用户安全性和体验(二)

源码

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: "https://[your-domain].com")
  
  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)
  }
}

后记

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

上一篇下一篇

猜你喜欢

热点阅读