首页 > 解决方案 > AWS Cognito 登录不起作用(Swift-iOS)

问题描述

我已将 cognito 集成到我的 xcode 项目中。注册/密码更新功能正常工作。但是我似乎无法让登录过程正常工作。我打开了日志,我收到以下错误

{"__type":"NotAuthorizedException","message":"Access Token has expired"}


Domain=com.amazonaws.AWSCognitoIdentityProviderErrorDomain Code=-1000 "Authentication delegate not set" UserInfo={NSLocalizedDescription=Authentication delegate not set}]

我还在AppDelegate脚本中实现了AWSCognitoIdentityInteractiveAuthenticationDelegate委托。

这是 AppDelegate 代码

class AppDelegate: UIResponder, UIApplicationDelegate {

    class func defaultUserPool() -> AWSCognitoIdentityUserPool {
        return AWSCognitoIdentityUserPool(forKey: userPoolID)
    }

    var window: UIWindow?
    var loginViewController: LoginVC?
    var navigationController: UINavigationController?
    var storyboard: UIStoryboard?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Warn user if configuration not updated
        if (CognitoIdentityUserPoolId == "us-east-1_TavWWBZtI") {
            let alertController = UIAlertController(title: "Invalid Configuration",
                                                    message: "Please configure user pool constants in Constants.swift file.",
                                                    preferredStyle: .alert)
            let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
            alertController.addAction(okAction)
            self.window?.rootViewController!.present(alertController, animated: true, completion:  nil)
            //print("Please configure user pool constants in Constants.swift file.")
        }

        // setup logging
        AWSDDLog.sharedInstance.logLevel = .verbose
        AWSDDLog.add(AWSDDTTYLogger.sharedInstance)

        // setup service configuration
        let serviceConfiguration = AWSServiceConfiguration(region: CognitoIdentityUserPoolRegion, credentialsProvider: nil)

        // create pool configuration
        let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoIdentityUserPoolAppClientId,
                                                                        clientSecret: CognitoIdentityUserPoolAppClientSecret,
                                                                        poolId: CognitoIdentityUserPoolId)

        // initialize user pool client
        AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)

        // fetch the user pool client we initialized in above step
        let pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
        self.storyboard = UIStoryboard(name: "Main", bundle: nil)
        pool.delegate = self


        return AWSMobileClient.sharedInstance().interceptApplication(
            application, didFinishLaunchingWithOptions:
            launchOptions)
        //return true
    }

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        if let navigationController = self.window?.rootViewController as? UINavigationController {
           if navigationController.visibleViewController is SummaryReportVC ||
              navigationController.visibleViewController is GoalStatusReportVC || navigationController.visibleViewController is YearTotalsReportVC || navigationController.visibleViewController is DailyActivityReportVC {
                return UIInterfaceOrientationMask.all
            } else {
                return UIInterfaceOrientationMask.portrait
            }
        }
        return UIInterfaceOrientationMask.portrait
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }

}

extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {

    func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
        print("Calling signin VC from app delegate")
        if (self.navigationController == nil) {
            self.navigationController = self.storyboard?.instantiateViewController(withIdentifier: "NCFirst") as? UINavigationController
        }

        if (self.loginViewController == nil) {
            self.loginViewController = self.navigationController?.viewControllers[0] as? LoginVC
        }

        DispatchQueue.main.async {
            self.navigationController!.popToRootViewController(animated: true)
            if (!self.navigationController!.isViewLoaded
                || self.navigationController!.view.window == nil) {
                self.window?.rootViewController?.present(self.navigationController!,
                                                         animated: true,
                                                         completion: nil)
            }

        }
        return self.loginViewController!
    } 
}

这是我的 LoginVC 代码

class LoginVC: UIViewController {

    @IBOutlet weak var loginButton: UIButton!
    @IBOutlet weak var forgotPasswordLabel: UILabel!
    @IBOutlet weak var signUpLabel: UILabel!
    @IBOutlet weak var emailTF: UITextField!
    @IBOutlet weak var passwordTF: UITextField!
    var passwordAuthenticationCompletion: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>?
    let pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
    var usernameText: String?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationController?.navigationBar.tintColor = UIColor.white
        self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
        self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
        self.navigationController!.navigationBar.shadowImage = UIImage()
        self.navigationController!.navigationBar.isTranslucent = true

        loginButton.addTarget(self, action: #selector(loginUser), for: .touchUpInside)

        loginButton.layer.cornerRadius = 18
        emailTF.addPadding(.left(35))
        passwordTF.addPadding(.left(35))

        let tap = UITapGestureRecognizer(target: self, action: #selector(goToForgotPasswordVC))
        let tap2 = UITapGestureRecognizer(target: self, action: #selector(goToSignUp1VC))
        forgotPasswordLabel.isUserInteractionEnabled = true
        forgotPasswordLabel.addGestureRecognizer(tap)
        signUpLabel.isUserInteractionEnabled = true
        signUpLabel.addGestureRecognizer(tap2)


    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.passwordTF.text = nil
        self.emailTF.text = usernameText
    }


    @objc func loginUser() {
        print("Got inside Login func")
        if (self.emailTF.text != nil && self.passwordTF.text != nil) {
            print("Calling login method now")
            let authDetails = AWSCognitoIdentityPasswordAuthenticationDetails(username: self.emailTF.text!, password: self.passwordTF.text! )
            self.passwordAuthenticationCompletion?.set(result: authDetails)

        } else {
            print("Empty fields")
            let alertController = UIAlertController(title: "Missing information",
                                                    message: "Please enter a valid user name and password",
                                                    preferredStyle: .alert)
            let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
            alertController.addAction(retryAction)
        }
    }

    @objc func goToActivitySessionsVC() {
        let storyboard = UIStoryboard(name: "TabBar", bundle: nil)
        let destVC = storyboard.instantiateViewController(withIdentifier: "TabBarVC")
        self.navigationController?.pushViewController(destVC, animated: true)
        self.navigationController?.isNavigationBarHidden = true
    }

    @objc func goToForgotPasswordVC() {
        let storyboard = UIStoryboard(name: "ForgotPassword", bundle: nil)
        let destVC = storyboard.instantiateViewController(withIdentifier: "ForgotPasswordVC")
        self.navigationController?.pushViewController(destVC, animated: true)
    }

    @objc func goToSignUp1VC() {
        let storyboard = UIStoryboard(name: "SignUp", bundle: nil)
        let destVC = storyboard.instantiateViewController(withIdentifier: "SignUp1VC")
        self.navigationController?.pushViewController(destVC, animated: true)
    }

 /*   func checkLoginStatus() {
        if !AWSSignInManager.sharedInstance().isLoggedIn {
            showSignIn()
        }
        else {
            print("Logged In")
            AWSSignInManager.sharedInstance().logout(completionHandler: {(result: Any?, error: Error?) in
                self.showSignIn()
                print("Sign-out Successful");

            })
        }
    }

}
*/
extension LoginVC: AWSCognitoIdentityPasswordAuthentication {

    public func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
        print("Get details called")
        self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
        DispatchQueue.main.async {
            if (self.usernameText == nil) {
                self.usernameText = authenticationInput.lastKnownUsername
            }
        }
    }

    public func didCompleteStepWithError(_ error: Error?) {
        print("Did commplete step with error called")
        DispatchQueue.main.async {
            if let error = error as NSError? {
                let alertController = UIAlertController(title: error.userInfo["__type"] as? String,
                                                        message: error.userInfo["message"] as? String,
                                                        preferredStyle: .alert)
                let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
                alertController.addAction(retryAction)

                self.present(alertController, animated: true, completion:  nil)
                 print(error.description)
            } else {
                self.emailTF.text = nil
                self.dismiss(animated: true, completion: nil)
                print("Got in else")
            }
        }
    }
}

要注意的另一件事是getDetails永远不会被调用,didCompleteStepWithError方法也是如此。当我单击登录按钮时,没有任何反应。

标签: iosswiftamazon-web-servicesamazon-cognitoaws-cognito

解决方案


AWS 文档非常混乱。经过多次试验和错误,我能够成功设置 Cognito、注册、登录时进行身份验证,并在注销时取消身份验证。老实说,我不完全明白为什么我称某些东西。尽我所能,我会在这里解释。

以下是 Cognito 的工作原理。首先它假定用户已经登录并通过了身份验证。它会检查这是否属实。这就是为什么故事板的入口点是用户登录看到的视图控制器的原因。这一切都是通过在启动时在 AppDelegate 中运行的代码完成的。更多关于你需要在下面修复的内容。

如果用户未登录,将调用 startPasswordAuthentication()。在您的代码中,(应该如此)这在协议 AWSCognitoIdentityInteractiveAuthenticationDelegate 的 AppDelegate 扩展中定义。此外,每次用户需要登录时都会调用 startPasswordAuthentication()。如果用户在应用程序启动时已经登录,则不会调用。

关于您的问题的另一个说明- getDetails 仅在用户未登录时才在启动时调用。如果在启动时用户未登录,则调用此方法。如果您登录然后退出,也会调用它。

因此,请确保您的故事板的入口点是登录屏幕。

在接下来的声明中,我并不完全确定,如果有,请随时纠正我:AWS 会在成功登录后自动访问入口点。您在 @objc func loginUser() 中进行的所有操作对我来说都是正确的。这就是我所拥有的。但是请确保您的入口点不是登录屏幕,而是成功登录后显示的内容。这是我的故事板的图片:

请参阅连接到 App View Controller 的入口点。 当用户启动应用但未登录时,视图会弹出到登录视图控制器。

尝试添加以下内容。我不太确定为什么这会起作用,但它会导致正确的身份验证:

在您的 AppDelegate 中,在情节提要的变量之后,添加一个布尔值 isInitialized,如下所示:

     var isInitialized : Bool = false

然后在设置 Cognito 配置后添加此代码。(就在您在 didFinishLaunchingWithOptions 中的 return 语句之前):

    let didFinishLaunching = AWSSignInManager.sharedInstance().interceptApplication(application, didFinishLaunchingWithOptions: launchOptions)

    if (!self.isInitialized) {
        AWSSignInManager.sharedInstance().resumeSession(completionHandler: { (result: Any?, error: Error?) in
            print("Result: \(String(describing: result)) \n Error:\(String(describing: error))")
        })
        self.isInitialized = true
    }

现在用以下内容替换您当前对 didFinishLaunching 的返回语句:

    return didFinishLaunching

确保您在登录屏幕的 viewDidLoad() 方法中设置了此委托(注意您必须导入 AWSAuthCore):

   AWSSignInManager.sharedInstance().delegate = self

并在您的登录 VC 中实现该协议,如下所示:

   extension LoginViewController : AWSSignInDelegate {
       func onLogin(signInProvider: AWSSignInProvider, result: Any?, error: Error?) {
           if error == nil {

           }
       }
   }

将这些变量作为类变量添加到用户登录后看到的视图控制器中。它们在下面被引用。

var user : AWSCognitoIdentityUser?
var userAttributes : [AWSCognitoIdentityProviderAttributeType]?

/*
     * :name: defaultUserPool
     * :description: Returns the cognito identity pool for the global pool ID.
     * :return: A Cognito Identity pool instantiation
     */
    class func defaultUserPool() -> AWSCognitoIdentityUserPool {
        return AWSCognitoIdentityUserPool(forKey: userPoolID)
    }

最后,确保在 viewWillAppear() 的初始屏幕中检查用户属性。例如在此方法中调用函数 fetchUserAttributes:

func fetchUserAttributes() {
    self.user = AppDelegate.defaultUserPool().currentUser()
    self.user?.getDetails().continueOnSuccessWith(block: { [weak self = self] (task) -> Any? in

        AWSSignInManager.sharedInstance().resumeSession(completionHandler: { (result: Any?, error: Error?) in
            print("Result: \(String(describing: result)) \n Error:\(String(describing: error))")
        })
        guard task.result != nil else {
            // alert error
            return nil
        }
        self?.username = self?.user?.username
        self?.userAttributes = task.result?.userAttributes
        self?.userAttributes?.forEach({ (attribute) in
            print("Name: " + attribute.name!)
        })
        DispatchQueue.main.async {
                self?.setAttributeValues()
            }
        }
        return nil
    })
}

func resetAttributeValues() {
        self.user = nil
        self.userAttributes = nil
}

最后,这是我的退出代码:

    let comp = { [weak self = self] (_ result: Any?, _ error: Error?) -> Void in
        if error == nil {
            self?.user?.signOut()
            self?.resetAttributeValues()
            self?.fetchUserAttributes()
        }
    }
    AWSSignInManager.sharedInstance().logout(completionHandler: comp)

我希望这有帮助。我知道这真的很令人困惑,老实说,我在写这篇文章时很困惑。祝你好运,如有任何问题,请随时给我发消息。


推荐阅读