4장 iOS Swift로 하는 local,facebookLogin

일단 기본적으로 swift을 알고 있다는 전제하에서 진행하겠습니다.(Object-c는 왜 없냐구요? 제가 못해요...)

대략 어떻게 하면 될까요?

대략적인 개요.

혹시 2, 3장을 읽고 이해하셨다면 바로 아시겠지만, 서버측을 제외하고 간단히 이야기를 하면 클라이언트는 ID,Password을 서버에 넘겨 줍니다. 그렇다면 서버는 json으로 token을 넘겨줄겁니다. 그러면 그 token을 local에 저장. 그 뒤 우리가 서버에 유저 정보가 필요한 요청을 할 때에는 token과 함께 요청을 보내면, 서버는 token으로 user을 인식하고 적당한 data을 반환해 줄 것 입니다.

환경 설정

일단 저는 인터넷 연결는 Alamofire라는 라이브러리를 사용할 예정입니다. 적당히 설치해 주시길 바랍니다. 혹시 cocoapod 등에 대해 모르시는 분은 검색해 주시길 바랍니다. 그 뒤에는 facebook developer 사이트를 들어가서 website를 추가했던것 처럼 ios를 추가해주시고 quickStart 을 그대로 따라해주시면 됩니다. 잘 모르시겠으면 검색하시면 많은 자료가 나옵니다. 여기까지 하시면 기본적인 환경설정이 끝났습니다.

스토리보드

일단 스토리 보드를 이런식으로 만들어 줍니다. 제일 앞에는 버튼 2개(localLogin, localSignup)을 만들어 줍니다. 그리고 viewController을 3개 추가. textField 2개와 버튼 1개 추가된 view를 2개. 그리고 버튼1개가 있는 view를 1개 만듭ㄴ다. 그리고 localLogin버튼을 누르면 LoginView로 이동하게 segue를 만들어 주시고, localSignup버튼을 누르면 SignupView로 이동하게 만들어 줍시다. 그리고 Manual Segue를 만들어 주는데 3개의 뷰가 HomeView로 가게 만들어 주시고 HomeView는 처음 View로 이동하게 Segue를 만들어 줍니다. Segue를 만드는 방법은

가장 왼쪽 버튼과 ctrl을 누르고 드래그 하시면 만들어 집니다. 총 4개의 Manual Segue입니다. Home으로 가는 Segue는 Identifier을 goHome 그리고 Home에서 처음 뷰로 가는 Segue는 goFirst로 해줍니다.

이제 스토리 보드는 끝났습니다.

실제 코딩

cocoa Touch Class을 3개 만들어 줍니다. 각각의 이름은 LoginViewController, SignupViewController, HomeViewController로 하겠습니다. 그리고 스토리보드에서 각각을 연결해 줍니다.

LoginViewController

import UIKit
import Alamofire

@IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

    @IBAction func loginButtonTapped(sender: AnyObject) {
        let email = emailTextField.text
        let password = passwordTextField.text

        let loginParams = [
            "email": email! as String,
            "password": password! as String
        ]
        Alamofire.request(.POST, "http://localhost:3000/api/login/local", parameters: loginParams, encoding: .JSON)
            .responseJSON { response in switch response.result {
            case .Success(let JSON):
                let response = JSON as! NSDictionary
                print(response.objectForKey("type"))
                if ((response.objectForKey("type")) != nil){
                    let token = response.objectForKey("token") as! NSString
                    let userDefaults = NSUserDefaults.standardUserDefaults()

                    userDefaults.setValue(token, forKey: "jsonWebToken")

                    self.performSegueWithIdentifier("goHome", sender: self)
                }
            case .Failure(let error):
                print("Request failed with error: \(error)")
                }
        }

SignupviewController

import UIKit
import Alamofire

class SignupViewController: UIViewController {

    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */
    @IBAction func signupButtonTapped(sender: AnyObject) {
        let email = emailTextField.text
        let password = passwordTextField.text

        let loginParams = [
            "email": email! as String,
            "password": password! as String
        ]
        Alamofire.request(.POST, "http://localhost:3000/api/login/local/signup", parameters: loginParams, encoding: .JSON)
            .responseJSON { response in switch response.result {
            case .Success(let JSON):
                let response = JSON as! NSDictionary
                print(response.objectForKey("type"))
                if ((response.objectForKey("type")) != nil){
                    let token = response.objectForKey("token") as! NSString
                    let userDefaults = NSUserDefaults.standardUserDefaults()

                    userDefaults.setValue(token, forKey: "jsonWebToken")

                    self.performSegueWithIdentifier("goHome", sender: self)
                }
            case .Failure(let error):
                print("Request failed with error: \(error)")
                }
        }


    }

}

HomeViewController

import UIKit

class HomeViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

    @IBAction func LogoutButtonTapped(sender: AnyObject) {
        let userDefaults = NSUserDefaults.standardUserDefaults()

        userDefaults.removeObjectForKey("jsonWebToken")

        self.performSegueWithIdentifier("goFirstView", sender: self)
    }
}

이런식으로 설정을 해줍니다. token은 NSUserDefaults에 저장을 하였습니다. NSUserDefaults는 앱이 꺼진뒤에도 계속 저장을 할수 있게 해줍니다. 모르면 검색검색. LoginViewController을 보면 입력받은 email, password을 서버에 post로 전달하고 받아온 값을 json 처리를 하여서 userDefaults에 저장을 합니다. signup도 똑같습니다.

HomeView는 버튼을 클릭할시 userDefaults에 있는 token을 지우고 첫화면으로 돌아가게 합니다.

이제 페이스북 로그인이 들어있는 ViewController을 봅시다.

import UIKit
import FBSDKCoreKit
import FBSDKLoginKit
import Alamofire

class ViewController: UIViewController, FBSDKLoginButtonDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        var loginButton = FBSDKLoginButton()
        loginButton.readPermissions = ["public_profile", "email", "user_friends"]
        loginButton.center = self.view.center

        loginButton.delegate = self

        self.view.addSubview(loginButton)
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(true)

        let userDefaults = NSUserDefaults.standardUserDefaults()

        print(userDefaults.objectForKey("jsonWebToken"))
        if (userDefaults.objectForKey("jsonWebToken") != nil) {
            let token = userDefaults.objectForKey("jsonWebToken")
            self.performSegueWithIdentifier("goHome", sender: self)
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: -FaceBook Login

    func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
        if error == nil {
            print("login completed")
            self.performSegueWithIdentifier("showNew", sender: self)

            let accessToken = FBSDKAccessToken.currentAccessToken()
            let req = FBSDKGraphRequest(graphPath: "me", parameters: ["fields":"email,name"], tokenString: accessToken.tokenString, version: nil, HTTPMethod: "GET")
            req.startWithCompletionHandler({ (connection, result, error : NSError!) -> Void in
                if(error == nil)
                {
                    print("result \(result)")
                    let userEmail : NSString = result.valueForKey("email") as! NSString
                    let loginParameter = [
                        "email": userEmail,
                        "fbToken": accessToken.tokenString
                    ]
                    Alamofire.request(.POST, "http://localhost:3000/api/login/facebook", parameters: loginParameter, encoding: .JSON)
                        .responseJSON { response in switch response.result {
                        case .Success(let JSON):
                            let response = JSON as! NSDictionary
                            let token = response.objectForKey("token") as! NSString
                            let userDefaults = NSUserDefaults.standardUserDefaults()

                            userDefaults.setValue(token, forKey: "jsonWebToken")
                        case .Failure(let error):
                            print("Request failed with error: \(error)")
                            }
                    }

                }
                else
                {
                    print("error \(error)")
                }
            })
        }
        else {
            print(error.localizedDescription)
        }
    }

    func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
        print("user logged out")
    }


}

FBSDKLoginButton을 만들고 받아오는 권한을 설정합니다. 그다음엔 적당히 중앙배치를 해줍니다. 그리고 viewDidAppear에서는 혹시 이미 userDefaults에 token이 있다면(로그인이 되어있다면) 바로 Home으로 넘어가게 해줍니다.

페이스북로그인 버튼을 누르고 로그인에 성공한다면 accessToken을 받아오고, email과name을 요청합니다. 그리고 요청이 성공할시 accessToken과 email을 서버에 보냅니다. 그리고 다시 서버에서 받아온 token 값을 저장한다면 완료입니다.

대략 이런식으로 하면 web과 iphone에서 같은 DB로 로그인을 구현할수 있게 됩니다.

다음은 android에서 로그인을 할 수 있게 만들어 보겠습니다.