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

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

作者: 刀客传奇 | 来源:发表于2018-12-01 12:59 被阅读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)
      }
    }
    

    后记

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/suwlcqtx.html