iOS Auth(3-2)Facebook整合-程式開發

這一篇是繼續上一篇未完成的,請先看完這一篇:

iOS Auth(3-1)Facebook整合-設定專案

這一篇就是程式碼的部份了,請確定你己經匯入好 Facebook SDK 與 Firebase SDK (Podfiel 中,應包括 Firebase/Core 與 Firebase/Auth),我們就可以開始了。

首先,是 AppDelegate.swift ,第一個動作是引入 SDK ,

import FBSDKLoginKit

Facebook 需要我們在這兒設兩個設定,第一個是在開啟時傳送選項在application(_:didFinishLaunchingWithOptions:)  加入

        FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)

這是用來告訴 Facebook 的程式,APP啟動了,使用的選項是如同參數

另一個是要在 application(_:open:options:),預設專案 AppDelegate 不會設定這個方法,這個方法是用來設定  URL Scheme 有關,也就是說,告訴 APP 如何和 Facebook 溝通。整方法內容如下:

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        let handled: Bool = FBSDKApplicationDelegate.sharedInstance().application(app, open: url, sourceApplication: options[.sourceApplication] as? String, annotation: options[.annotation])
        return handled
}

然後,我們就可以設計 UI 了

螢幕快照 2018-02-05 上午1.33.09.png

畫面上有兩個物件,上面是 Facebook  Login / Out  Button,拉一個 UIView 然後把 Class 改為 FBSDKLoginButton 就可以了。 另一個是訊息,是一個標準的 UILabel。把這個 UI 物件 拉到 ViewContorller 中成為兩個 IBOutlet 我們就可以開始看 ViewController.swift 的程式了。

程式碼也很簡單,我們首先要對 Facebook Login Button 做兩個設定,第一個是把 delegate 設為 ViewController ,另一個是設定權限要求。在 ViewDidLoad 中 加入下面兩行就可以了。

        facebookLoginButton.delegate = self
        facebookLoginButton.readPermissions = ["public_profile","email","user_friends"]

把 Delegate 設為 self 以便接收登入/登出時的時機,為了當 Delegate ,ViewController也要同時繼承 FBSDKLoginButtonDelegate 並新增兩兩方法,一個是登入完成的,另一個是登出的

先講登入完成的 loginButton(_ loginButton:, didCompleteWit result:, error:),在這個方法執行時,其實不定成功登入,也許會因為網路不通或使用者取消等原因而失敗,但若登入成功,則會回傳一個 FBSDKLoginManagerLoginResult 物件,其中就有我們需要的東西了。

成功登入 Facebook 後,就要利用登入資訊來登入到 Firebase 中。要作這件事,就要用 Firebase SDK 提供的 Auth 物件,這個物件是 Firebase 身份驗證的服務界面,登出登入查使用者都要用到它。取得它的實體很簡單,只要用 Auth.auth() 方法即可,然後.signIn(with: AuthCredential, completion:) 這個方法需要一個 AuthCredential,是一個登入的憑證,主要是如何存取 Facebook , 也就是用 Facebook 回傳的資料所產出的。

在 Facebook 回傳的 user  物件中,我們可以由  FBSDKAccessToken.current()?.tokenString 取得 存取 Facebook 令牌,有了個之後,就可以用 FacebookAuthProvider.credential 來產出 AuthCredential,我們就可以用它來登入 Firebase 了。

最後若是成功,就把顯示名稱秀出來。

    func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
        if error == nil{
            if let accessToken = FBSDKAccessToken.current(){
                let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
                Auth.auth().signIn(with: credential, completion: { (user, error) in
                    if error == nil{
                        if let user = Auth.auth().currentUser{
                            self.message.text = "歡迎:" + (user.displayName ?? "")
                            let alertView = UIAlertController.init(title: "登入成功", message: "FB Sing in with:" + (user.email ?? ""), preferredStyle: UIAlertControllerStyle.alert)
                            alertView.addAction(UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                            self.present(alertView, animated: true, completion: nil)
                        }
                    }else{
                        print(error?.localizedDescription)
                    }
                })
            }
        }else{
            print(error.localizedDescription)
        }
    }

登出就比較簡單了,因為這是登出之後再回傳,所以不用檢查成功與否,秀訊息然後登出就可以了。

 

    func loginButtonDidLogOut(_ loginButton: FBSDKLoginButton!) {
        message.text = "請登入"
        let alertView = UIAlertController.init(title: "登出", message: "FB Sing Out", preferredStyle: UIAlertControllerStyle.alert)
        alertView.addAction(UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil))
        self.present(alertView, animated: true, completion: nil)        
        do {
            try Auth.auth().signOut()
        } catch  {
            print(error.localizedDescription)
        } 
    }

執行的畫面:

 

最後,到Firebase 的控制台,也能看到這個帳號,這個整合就算完成了。

螢幕快照 2018-02-05 上午2.33.42

AppDelegate.swift

import UIKit
import Firebase
import FBSDKLoginKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
        return true
    }

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        let handled: Bool = FBSDKApplicationDelegate.sharedInstance().application(app, open: url, sourceApplication: options[.sourceApplication] as? String, annotation: options[.annotation])
        return handled
    }
}

 

ViewController.swift

import UIKit
import FirebaseAuth
import FBSDKLoginKit

class ViewController: UIViewController,FBSDKLoginButtonDelegate {

    @IBOutlet weak var facebookLoginButton: FBSDKLoginButton!
    @IBOutlet weak var message: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        facebookLoginButton.delegate = self
        facebookLoginButton.readPermissions = ["public_profile","email","user_friends"]
    }

    //MARK:Facebook Login Button Delegate
    func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
        if error == nil{
            if let accessToken = FBSDKAccessToken.current(){
                let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
                Auth.auth().signIn(with: credential, completion: { (user, error) in
                    if error == nil{
                        if let user = Auth.auth().currentUser{
                            self.message.text = "歡迎:" + (user.displayName ?? "")
                            let alertView = UIAlertController.init(title: "登入成功", message: "FB Sing in with:" + (user.email ?? ""), preferredStyle: UIAlertControllerStyle.alert)
                            alertView.addAction(UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                            self.present(alertView, animated: true, completion: nil)
                        }
                    }else{
                        print(error?.localizedDescription)
                    }
                })
            }
        }else{
            print(error.localizedDescription)
        }
    }

    func loginButtonDidLogOut(_ loginButton: FBSDKLoginButton!) {
        message.text = "請登入"
        let alertView = UIAlertController.init(title: "登出", message: "FB Sing Out", preferredStyle: UIAlertControllerStyle.alert)
        alertView.addAction(UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil))
        self.present(alertView, animated: true, completion: nil)
        do {
            try Auth.auth().signOut()
        } catch  {
            print(error.localizedDescription)
        }
    }
}

iOS Auth(3-1)Facebook整合-設定專案

雖然 Firebase 提供了自訂與多種認證方式,但若想要讓使用者簡單一點使用社群認證時,Google 自家的整合認證用起來是最方便不過的,但是若是在台灣,Facebook 才是最多人使用的社群軟體,若你的APP整合了 Facebook 帳號,在帳號認證時,大多數人就不會考量太多事,就會登入你的 APP了。

首先請參考之前的文章,先引入SDK的部份,這和匿名認證所需的一樣,在 Podfile 中要加上  ,然後重新 pod install 一次,請參閱 iOS Auth(1)匿名認證 中有關引入SDK 的部份。

接下來我們就開始 Facebook 認證整合的部份了。和 Facebook 整合和 Google 不同,由為不是自家整合,所以我們有許多事情要在 Facebook 設定,而不能只用 Firebase 幫我們設定,或用同樣的方法整合。要在 Facebook 設定相關的開發,你需要一個 Facebook 帳號,並在Facebook 開發者網站,建立一個 APP 做為登入使用。Facebook 開發者網站在

https://developers.facebook.com

你若是第一次使用,要作一些電話號碼認證等程序,也不是很麻煩就是一個簡訊認證,也就是你必需要有手機門號,登記在 Facebook 上才能進行開發

首先按下右上角的開始註冊

螢幕快照 2018-02-03 下午11.32.27

按下一步

螢幕快照 2018-02-03 下午11.32.45

準備好你的手機號碼,接收簡訊

螢幕快照 2018-02-03 下午11.33.44

終於,我們可以開始建立 Facebook APP 了

螢幕快照 2018-02-03 下午11.34.20

Facebook 的部份,佔時設定到這兒,我們先到 Firebase 的控制台,試者開啟 Facebook 認證畫面。我們進入控制台,選擇應用程式,按下 Authentication 後指定登入方式,開啟 Facebook 認證後,就出現了以下的畫面:

螢幕快照 2018-02-04 上午2.52.57

這個畫面除了開啟外,還要輸入兩件事情,一個是應用程式的 ID,一個是應用程式的金鑰。這兩個東西,實際指的是 Facebook APP 的 ID 及金鑰,可以在 Facebook 的開發者頁面取得,而在這個頁面的最下方有一個連結,也是我們等一下需要設定給 Facebook 的回傳位置,可以先把它記下來,也就是說,這些資料是 Facebook 與 Firebase 溝通的依據。

在Facebook 開發者頁面,指定應用程式後,進入設定基本資料頁面,就可以看到在畫面的最前兩筆資料,【應用程式編號】與【應用程式密鑰】就是我們開啟 Firebase 的 Facebook 認證的

螢幕快照 2018-02-04 上午3.12.21

依據,把它填回 Firebase 先完成專案的設定。

接下來,在 Facebook 的這個畫面中,最下方有一個新新增台的按鈕,是我們用來設定連結行動 APP 的地方,按下它後,新增一個 iOS APP,我們就有了以下的畫面:

螢幕快照 2018-02-04 上午3.25.34.png

螢幕快照 2018-02-04 上午3.32.32.png

在這個畫面中,我們一定要設的是【套件組合編號】也就是你 iOS APP 的 Bundle ID,開啟單一登入,並填上【隱私權政策網址】。【隱私權政策網址】是個有點麻煩的事,若你是公司就要認真的依據國發會的建議寫一個,但若只是個人測試開發,那就自己設個部落格或網頁,抄一個範本作一個。但這個不能不作,因為亂寫可能會被停用

國發會政府網站版型與內容管理規範

最後按下右上的啟用與左下的儲存變更就可以了。

若沒有任可問題,可以按下左側欄最下面【產品+】這個按鈕,來新增我們真正的目的。
螢幕快照 2018-02-04 下午7.30.52.png

我們要做的是 Facebook 登入,所以就選擇【Facebook 登入】那一個項目,然後按下 iOS 我們就可以開始 Facebook 登入的部份了。按下之後我們就可以依據指示一步步依指示完成這個工作,若不小心關掉了畫面,也可以在左側欄的 Facebook 登入 找到這個頁面。這個指示一共有九個動作,我們一個一個完成這些動作。

螢幕快照 2018-02-04 下午11.34.46

第一,二步是下載 Facebook iOS SDK,匯入它,對熟手來說應該都不是問題,基本上下載後拉進 Xcode 中的 Frameworks 群組就可以了,我們只需要 Bolts,FBSDKCoreKit 和 FBSDKLoginKit 三個項目。我的建議是把 Copy items if Needs 保持勾選,不然還要設定 Search Paths

第三步是輸入套件名稱,也就是我 iOS APP 的 Bundle ID

螢幕快照 2018-02-05 上午12.13.16

第四步是啟用應用程式的單一登入,若還沒有打開,打開它就是了

第五個動作是修改 info.plist 檔,這個 Facebook 建議的方法和我們平常編輯 info.plist 的方法不太一樣,因為要設定的項目不少,所以直接把 XML 顯示出來,複製到適當的地方就好了,因為只是複製貼上,系統不會幫我們檢查正確性,所以要查看一到 XML 中的各屬性是否正確。

要把 XML 貼到 info.plist 開啟,就要在Xcode 的該檔案中,按滑鼠右鍵,並指定為 Open As > Source Code  就可以用原始碼開啟了,把 Facebook 的兩段 XML 貼在 <dict> …… </dict> 的中間就可以了

螢幕快照 2018-02-05 上午12.24.13.png

 

接下來,第六七八九的動作,都是程式碼的部份,我會在下一篇詳細討論,基本上照打就可以了,但Facebook 在本文撰寫時,還只提供 Objective-C 的程式碼,Swift 要自己轉譯,或參考下一篇,所以設除程式外的設定也就差不多了。

最後,還有一件事情要做,就是要把之前在 Firebase 設定時,畫面有一個  OAuth 重新導向 URL複製到 Facebook APP 中。

螢幕快照 2018-02-04 上午2.52.57

設定的方法就是在 Facebook 開發者網頁,按下右側的 Facebook 登入 > 設定中,看到相同的欄位,把它貼上就好了。

螢幕快照 2018-02-05 上午12.39.49.png

到此為止,所以【設定】的部份就算完成了。

我們下一篇再來談程式的部份……未完待續。

Android Auth(2-2)Google 整合-完成認證

這一篇是根據上一篇的後續,如果還沒看過上一篇,請先參閱:

Android Auth(2-1)Google 整合認證-設定環境/UI

這樣我們就可以開始寫程式了,我們要實作 Firebase 整合 Google 認證登入,需要了解三個物件,兩個與 Google 帳號有關,另一個和 Firebase Aentication 相關,分別介紹如下:
SignInButton:這是用來啟動 Google 登入頁的 View,這在前一篇設定 UI 時,己經介紹過了
GoogleApiClient :這是我們用來和 Google 登入溝通協調的工具,包括UI 的產生等
FirebaseAuth:這是我們用來和 Firebase 登入溝通協調的工具

而登入的主流程如下:

  1. 建立以上三物件的實體
  2. 由 SignInButton 啟動用 GoogleApiClient 所產出的登入 Intent 並等待回傳
  3. GoogleApiClient 回傳登入成功與否,若成功就使用該帳號登入 Firebase
  4. 若成功就顯示相關登入者名稱資訊

另外,還有一個登出的方法需要實作

第一步,來產出物件的實體,SignInButton 我們在介面檔己經作出來了,所以只需要連繫到就可以了,FirebaseAuth 的實體也很簡單,只要用 .getInstance() 方法就可以取得了, GoogleApiClient 就麻煩一點,要先建立一個設定用的 GoogleSignInOptions 的實體,再用Builder 方法來產生,在產生時,還要設定連線失敗回報的機制,程式碼如下:

signInButton = (SignInButton)findViewById(R.id.singinButton);

// 設定 FirebaseAuth 介面
mAuth = FirebaseAuth.getInstance();

// 設定 Google 登入 Client
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(getString(R.string.default_web_client_id))
        .requestEmail()
        .build();
mGoogleApiClient = new GoogleApiClient.Builder(getApplicationContext())
        .enableAutoManage(this, new GoogleApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
                Toast.makeText(MainActivity.this,"Google 連線異常",Toast.LENGTH_LONG).show();
            }
        })
        .addApi(Auth.GOOGLE_SIGN_IN_API,gso)
        .build();

這些程式都是寫在 OnCreate() 方法內。

接下來要把 signInButton 按下時,啟動登入頁面,同樣的寫在 OnCreate() 內,程式碼如下

signInButton.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View view){
        Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
        startActivityForResult(signInIntent, RC_SIGN_IN);
    }
});

 

\這兒,我用了一個常數 RC_SIGN_IN 做為回傳判別,需要在 class 最前面,設定這個

private  static final  int RC_SIGN_IN = 1;

 

然後,我們就要寫回傳時要做的事了,若成功,就試登入 Firebase,仍是成功就顯示出使者的 display name,在程式中新增兩個方法:

 

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == RC_SIGN_IN){
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        if (result.isSuccess()){
            GoogleSignInAccount account = result.getSignInAccount();
            //取得使用者並試登入
            firebaseAuthWithGoogle(account);
        }
    }
}

//登入 Firebase
private  void firebaseAuthWithGoogle(final GoogleSignInAccount account){
    AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(),null);
    mAuth.signInWithCredential(credential)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (!task.isSuccessful()) {
                        Toast.makeText(MainActivity.this, "Failed", Toast.LENGTH_LONG).show();
                    }else {
                        Toast.makeText(MainActivity.this, "SingIn name:"+account.getDisplayName(), Toast.LENGTH_LONG).show();
                    }
                }

            });
}

 

最後是登出的方法

public void firebaseSingOut(View view){
    // Firebase 登出
    mAuth.signOut();
    
    // Google 登出
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id))
            .requestEmail()
            .build();
    GoogleSignIn.getClient(this, gso).signOut().addOnCompleteListener(new OnCompleteListener<Void>() {
        @Override
        public void onComplete(@NonNull Task<Void> task) {
            Toast.makeText(MainActivity.this, "SingOut", Toast.LENGTH_LONG).show();
        }
    });
}

這樣就完成了我們的程式,執行結果如下

f206f207.PNG

也可以在 Firebase 的控制台中,看到我們登入的帳號:

f208

如此,我們就完成了帳號的登入了。

最後,附上完整的 MainActivity.java

public class MainActivity extends AppCompatActivity {
    private FirebaseAnalytics mFirebaseAnalytics;

    private SignInButton signInButton;
    private Button singOutButton;

    private  static final  int RC_SIGN_IN = 1;

    private GoogleApiClient mGoogleApiClient;
    private FirebaseAuth mAuth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Obtain the FirebaseAnalytics instance.
        mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
        singOutButton = (Button)findViewById(R.id.singoutButton);


        signInButton = (SignInButton)findViewById(R.id.singinButton);

        // 設定 FirebaseAuth 介面
        mAuth = FirebaseAuth.getInstance();

        // 設定 Google 登入 Client
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();
        mGoogleApiClient = new GoogleApiClient.Builder(getApplicationContext())
                .enableAutoManage(this, new GoogleApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
                        Toast.makeText(MainActivity.this,"Google 連線異常",Toast.LENGTH_LONG).show();
                    }
                })
                .addApi(Auth.GOOGLE_SIGN_IN_API,gso)
                .build();
        signInButton.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view){
                Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
                startActivityForResult(signInIntent, RC_SIGN_IN);
            }
        });

    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RC_SIGN_IN){
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()){
                GoogleSignInAccount account = result.getSignInAccount();
                //取得使用者並試登入
                firebaseAuthWithGoogle(account);
            }
        }
    }

    //登入 Firebase
    private  void firebaseAuthWithGoogle(final GoogleSignInAccount account){
        AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(),null);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (!task.isSuccessful()) {
                            Toast.makeText(MainActivity.this, "Failed", Toast.LENGTH_LONG).show();
                        }else {
                            Toast.makeText(MainActivity.this, "SingIn name:"+account.getDisplayName(), Toast.LENGTH_LONG).show();
                        }
                    }

                });
    }

    public void firebaseSingOut(View view){
        // Firebase 登出
        mAuth.signOut();

        // Google 登出
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();
        GoogleSignIn.getClient(this, gso).signOut().addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                Toast.makeText(MainActivity.this, "SingOut", Toast.LENGTH_LONG).show();
            }
        });
    }
}

Android Auth(2-1)Google 整合-設定環境/UI

要先建立己整合好 SDK 的環境,若你還沒有引入 SDK ,請先看

Android 專案引入 SDK

要使用匿名登入,第一件事要到 Firebase 控制台,開啟該項設定,設定方式很簡單,到控制台,找到我們要用的專案後,按下 Authentication  後選擇登入方式,最後開啟 Google 整合登入後,按下儲存就可以了:

螢幕快照 2018-01-31 上午2.18.18

接下來是要設定程式庫了,我們要在 APP 級的 build.gradle 檔案中,加入我們要用的部份,其實只有一行,在該檔案的 dependencies 段,加上

compile 'com.google.firebase:firebase-auth:11.8.0'
compile 'com.google.android.gms:play-services-auth:11.8.0'

f204.PNG

然後按下右上角的 Sync Now 就可以了。要注意的是,也許用的版本號碼和我用的不一樣,那個版本號碼與 com.google.firebase:firebase-core 有相依性,所以必時,要調整版號,如果一切 OK 的話,那我們就可以開始寫 Code 了。

首先,我們看一下畫面的設計:

f205.PNG

這上面有兩個按鈕,一個用來整合 Google 登入,另一個用來登出,而登入登出後,成功失敗的狀態都用 Toast 顯示出來,我們先看一下 activity_main.xml 檔,重點只有這兩個鈕的部份:

<com.google.android.gms.common.SignInButton
    android:id="@+id/singinButton"
    android:layout_width="200sp"
    android:layout_height="44sp" />
<Button
    android:id="@+id/singoutButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Sing Out"
    android:onClick="firebaseSingOut"
    app:layout_constraintTop_toBottomOf="@+id/singinButton" />

所以上方是一個 Google SDK 提供的 SignInButton ,用來啟動Google 登入頁面,它會自己因狀況來調介面包括語言等,我們只需要設好大小位置就好了。而下方是一個常見的 Android Button 而我們把它的 onClick 設到 “firebaseSingOut" 以便登出。

這樣,我們要準備的一切就好了,下一篇就是實作程式碼了。

Android Auth(1)匿名登入

若你尚未在引入 SDK 就要先看這一篇

在 Android 專案引入 SDK

有了建好的環境完成引入 Core SDK 之後,就要開始我們的工作了。我們的重點是要 做一個 APP 並使用匿名登入。匿名登入有許多用途,最重要的就是安全性問題,雖是匿名,我們還是可以知道若個APP 登入了,而不是駭客登入,別的服務存取時,也可以有依據。

要使用匿名登入,第一件事要到 Firebase 控制台,開啟該項設定,設定方式很簡單,到控制台,找到我們要用的專案後,按下 Authentication  後選擇登入方式,最後按下匿登入啟用後儲存就可以了。

螢幕快照 2018-01-29 上午12.21.37

接下來是要設定程式庫了,我們要在 APP 級的 build.gradle 檔案中,加入我們要用的部份,其實只有一行,在該檔案的 dependencies 段,加上【 compile ‘com.google.firebase:firebase-auth:11.8.0’】,然後按下右上角的 Sync Now 就可以了。要注意的是,也許用的版本號碼和我用的不一樣,那個版本號碼與 com.google.firebase:firebase-core 有相依性,所以必時,要調整版號,如果一切 OK 的話,那我們就可以開始寫程式了。
f201.PNG

程式的部份,我們暫時不做任可介面,只在開啟時,試著使用匿名登入,不管成功失敗,都回傳訊息。要做到這一件事,就要使用 FirebaseAuth 物件,該物件是我們和 Firebase 認證所需要的介面管道,登入登出和了解登入狀態都靠這個物件。而要取得該物件的實體,就要用 FirebaseAuth.getInstance() 方法。我們取得實體後再使用  .signInAnonymously() 就可以匿名登入了。而這些都寫在 MainActivity 的 onCreate 方法就可以了。先看一下程式碼:

public class MainActivity extends AppCompatActivity {
    private FirebaseAnalytics mFirebaseAnalytics;
    private FirebaseAuth mAuth;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAuth = FirebaseAuth.getInstance();
        mAuth.signInAnonymously().addOnCompleteListener(new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if(task.isSuccessful()){
                    Toast.makeText(MainActivity.this,"匿名登入成功 uid:\n" + mAuth.getCurrentUser().getUid(),Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(MainActivity.this,"匿名登入失敗",Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}

在上面的程式中,我們把 FirebaseAuth 存在 mAuth 這個變數中,然後使用這個實體執行 ..signInAnonymously() 方法,最後在完成時,依據成功或失敗的狀態,把訊息使用 Toast 顯示在畫面上,若成功,就顯示使用者的 uid,否則,就顯示失敗。如此,我們就完成了匿名登入的動作。

下面的執行的畫面:
Screenshot_1517577633

然後,我們在控制台的使用者清單中,也會找到相同的 UID 的匿名使用者。

f202.PNG

iOS Auth(2-3)Google 整合-完成整合

在上一篇,我們完成了 Google 認證的基礎:

iOS Firebase Authentication 認證系統(2-2 )Google 整合認證-介面與登入Google
接下來,我們要繼續完成後續的工作。

我們之前在 GIDSingInUIDelegate 的sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) 方法中,若是使用者成功登入,就加入了在畫面中顯示使用者名稱的工作,而我們就是要在這兒,完成登入到 FirebaseAuth 的工作。

要作這件事,就要用 Firebase SDK 提供的 Auth 物件,這個物件是 Firebase 身份驗證的服務界面,登出登入查使用者都要用到它。取得它的實體很簡單,只要用 Auth.auth() 方法即可,其實我在匿名登入那一篇,己經用過這個物件了。

iOS Firebase Authentication 認證系統(1)匿名認證

在那一篇,我用的是 .signInAnonymously 方法,而我現在要用的是有帳號的 signIn(with:completion:)  方法,這要使用一個憑證來登入,而這個所謂的憑證主要是 OAuth 之後回傳的使用者的辨識碼(ID) 與通行令牌(Access Token)所組成,也就是說 Google 登入認證回傳的授權中,就會有這兩個東西了。

在 Google 回傳的 user  物件中,我們可以由  user.authentication.idToken 取得使用者 ID ,並由 user.authentication.accessToken 取得令牌,有了這兩個之後,就可以用GoogleAuthProvider.credential(withIDToken: , accessToken: ) 方法來產出這個憑證實體。所以,我們就可以用這個實體來實作登入了。在 Auth 的 signIn(with:completion:)  方法,登入完成也需要一段程式來處理完成後的事情,登入完成若成功的話要處理的就是顯示成功登入的使用者名稱,不過這是由 Firebase 取得名稱,並設一個 UIAlertController 把成功或失敗的訊息秀出來。最後的這個 Alert 除了讓使用者知道之外呢,還有一個原因,由於整個登入動作是背景作業,所以回傳時是不同的執行緒在回傳,iOS 在非主執行緒操作 View 物件時,畫面不會更新,所以完整的寫法應該是寫多執緒的寫法,但多執行緒的寫法是相當容易出錯的,為了避免不必要的錯誤,所以加一個 Alert 強制畫面更新。

另外在完成登入後,就不需要再登入了,所以就可以把 google 登入頁停用了。案例中,我把它拉成一個 googleSingInButton 的 IBOutlet,再把它的 isEnable 設為 false 就可以了。

我們最後的 Google 登入的方法就改寫成這樣:

//MARK:GIDSignInDelegate
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        //若成功登入 Google
        if error == nil{

            // 取得授權資料,由於是 Optional 資料,所以先解 Optional
            if let authentication = user.authentication{
                //使用授權資料的 ID 與 存取令牌,產生 Google 登入憑證
                let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
                //使用登入憑證登入到 FirebaseAuth
                Auth.auth().signIn(with: credential, completion: { (firebaseuser, error) in
                    if error == nil{
                        //若成功登入後,就把登入按鈕設為停用,避免重覆登入
                        if let userName = firebaseuser?.displayName{
                            self.loginMessage.text = "Hello Firebase User: \(userName)"
                            self.googleSingInButton.isEnabled = false
                            let alertView = UIAlertController.init(title: "登入成功", message: "歡迎:\(userName)", preferredStyle: UIAlertControllerStyle.alert)
                            alertView.addAction(UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                            self.present(alertView, animated: true, completion: nil)
                        }
                    }else{
                        print(error?.localizedDescription)
                        let alertView = UIAlertController.init(title: "登入失敗", message: "原因:\(error!.localizedDescription)", preferredStyle: UIAlertControllerStyle.alert)
                        alertView.addAction(UIAlertAction.init(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alertView, animated: true, completion: nil)
                    }
                })
            }
        }else{
            print(error.localizedDescription)
        }
    }

 

做到這兒,這個專案只有最後一個東西要處理,就是登出,方法也很簡單,就是用 Auth 物件的 signOut 方法,沒有任何參數,但是它是一個會產生例外的方法,所以要包在 do-catch 中間。

實作就是把 logout 拉成一個 IBAtion,內容如下

   @IBAction func logout(_ sender: Any) {
        do {
            //試登出
            try Auth.auth().signOut()
            GIDSignIn.sharedInstance().signOut()            
            //若成功就啟用登入 button 並把訊息設成預設值
            self.googleSingInButton.isEnabled = true
            self.loginMessage.text = "請選擇登入方式"
        } catch  {
            print(error.localizedDescription)
        }
    }

 

到此,我們才完成了一個完整的登入,也可以在 Firebase 主控台看到登入的狀況,功能算是完整的了,但登入需要數秒的時間,所以要等一下畫面才會變化,

登入的畫面:
螢幕快照 2018-02-01 下午11.32.00.png

確實以Danny Shen 的帳號登入了畫面, Google 登入也變成灰色了,再來到 Firebase 主控台,我們也看到了該名使用者的登入碼了。
螢幕快照 2018-02-01 下午11.33.28

到此,我們己經完成了 Google 登入的所有任務了,接下來我們看看其他的帳號登入方法。

 

 

iOS Auth(2-2)Google 整合-介面與登入

這篇是完成上一篇
iOS Firebase Authentication 認證系統(2-1)Google 整合認證-設定環境

之後的動作,若還沒看過,請先看上一篇。

我們要作的畫面如下:

螢幕快照 2018-02-01 下午2.36.21

有三個元件在畫面上,第一個是 Google OAuth 整合登入鍵,中間的是登出鍵,最下方是一個訊息文字,這篇要做的事就是按上面的按鈕,完成登入後,顯示登入者名稱,如下圖

螢幕快照 2018-02-01 下午2.40.43.png

首先,開啟你的 storyboard,加上三個元件

螢幕快照 2018-02-01 下午2.30.32.png

為了讓讀者看的更清楚,我把所以物件都設了一個背景色,讀者不一定需要照作。

最上面一個是一個客製化的 View,先拉一個 UIView,然後把它設為自定的 Class 叫 GIDSingInButton。這是一個Google 提供的 OAuth 整合的頁面與UI,如果你有照 前一篇 設定好 SDK,應該就會正常出現了。

中間那一個是一個 UIButton 使用標準的 UIButton 就可以了,是用來登出用的。因為我們使用的方式,會記住登入過的使用者,若不做登出,就會使用上一次的使用者自動登入,造成APP內無法切換使用者,只能刪除 APP 重新安裝,才能使用其他帳號登入,若你有讓使用者切換帳號的需求的話,就必需要讓使用者登出。

下面這個元件是讓使用者知道目前的狀況訊息,所以我們拉一個 UILabel 並設定預設的文字。接下來是程式碼的部份了。

螢幕快照 2018-02-01 下午3.03.45.png

先說明一下  GIDSignIn 這個物件,這個物件需要 clientID 設置為登錄才能正常工作,這個ID是來自Google API控制台的應用的客戶端ID。也就是說我們本來需要到 Google API,設定一堆東西之後,再把該 ID 取回設定在APP中,但是因為我使用了 Firebase,所以這一部份可以由Firebase 設定即可取得該 ID 所以設定的方法是在 ViewController 的 viewDidLoad 中,加入:

GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID

另外 GIDSignIn 在完成登入後,需回到一個頁面,並把登入資訊交付給特定的物件,這需要使用 GIDSingInUIDelegate 與 GIDSignInDelegate 兩個協定來完成,我們把 ViewController 擴充後,把它都設給自己。在 GIDSignInDelegate 中,有一個方法是

func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!){}

這是用來接收登入完成的方法,若入成功,會回傳使用者的所有訊息,若登入失敗(例如使用者接取消),都會使用這個方法來接收,我們先做一個簡單的處理,若沒有錯誤,就把下方訊息欄(拉成叫 loginMessage 的 IBOutlet)顯示使用者名稱,回傳的顯示名稱可以在 user.profile.name 中找到。所以整個程式碼就變成:


 

import UIKit
import Firebase
import FirebaseAuth
import GoogleSignIn

class ViewController: UIViewController,GIDSignInUIDelegate,GIDSignInDelegate {
    @IBOutlet weak var loginMessage: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()

        // Google Sing in Login
        GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
        GIDSignIn.sharedInstance().delegate = self
        GIDSignIn.sharedInstance().uiDelegate = self
    }

    //MARK:GIDSignInDelegate
    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
        if error == nil{
            if let userName = user.profile.name{
                 loginMessage.text = "Hello Google User: \(userName)"
            }
        }else{
            print(error.localizedDescription)
        }
    }
}

若一切成功,開始執行後,按下Login 鈕,應該看到【類似】這樣的畫面:
螢幕快照 2018-02-01 下午2.55.27

為什麼說【類似】呢?因為這是 OAuth,也就是說,畫面狀況是Google決定的,Google可能會因為來源,使用記錄,系統版本,作業系統等各種原因自行決定畫面,我們不需要也不能介入的,以上面這個畫面來說,因為我有一個帳號在這台機器登入過,我刪除後重新安裝,它有發現有層經登入過但沒有其他資料時,就會預設把以前在這台機器上成功登入過的帳號叫出來,若你使用的帳號有一些問題(如疑似被盗用)也會出現一些不同的訊息。不管如何,在完成驗證之後,他就會把狀況回傳給我們設定的代理人 也就是 ViewController 來處理。最後我們就會看到結果了。

螢幕快照 2018-02-01 下午2.40.43

 

不過,這只是成功 SingIn 到 Google 而己,尚未整合到 Firebase,若你到 Firebase 主控台查看的話,會發現使用者並沒有登入到 Firebase,我們只是利用 Firebase 幫我們整合了 Google 帳號的 API 設定。

螢幕快照 2018-02-01 下午5.04.14.png

若只是這樣的話,我們只需要使用 GoogleSingIn SDK 也可以完成,但我們要的是讓使用者可以用 Google 帳號,也可以用 Facebook, Twitter 或自訂電子郵件帳號,並用單一方法處理的話,那我們就要把這個帳號登入到 Firebase 中。

另外,我們還需要處理帳號記錄與登出的工作要處理,這部份下一篇再來討論它好了。

 

iOS Auth(2-1)Google 整合-設定環境

雖然 Firebase 提供了自訂與多種認證方式,但若想要讓使用者簡單一點使用社群認證時,Google 自家的整合認證用起來是最方便不過的,因為是自家的軟體,所以整合時,己把第三方認證需要的程式都包含在其中了,Google 帳號的普及律也足夠,所以我們先從最簡單的 Google 帳號整合開始。

首先請參考之前的文章,先引入SDK的部份

iOS Firebase Authentication 認證系統(1)匿名認證

只要作到引入 SDK 部份就好了,接下來的動作也不困難,只要注意幾個觀念就好了。

接下來,前面的動作要重覆一次,因為我們要用 Google 帳號,所以在 Podfile 中要加上 Google/SignIn ,然後重新 pod install 一次

螢幕快照 2018-02-01 下午2.12.51

 

 

螢幕快照 2018-02-01 下午2.14.49

首先由於 iOS APP 之間呼叫的方式,由於是整合 Google 帳號,所以要先離開APP,使用 Google APP 或網頁,登入完成之後再回來,所以我們要在APP 建立一個開啟的方式,叫 URL Scheme,也就是說 tel://02-3456-7890 這個 URL 就知道要打電話, https://www.apple.com 這個 URL就知道要開網頁,那也可自己設一個呼叫自己。

在設定它之前先要在 Firebase Web 主控台開啟 Google 整合驗證功能:

螢幕快照 2018-01-31 上午2.18.18

接下來就要設定 URL Scheme 了,設定的方法也很簡單,按下 專案 – APP Target – Info 項目內,會看到 URL Types 的設定,新增兩個就好了,其中一個是你的 Bundle ID,另一個要到最初引入SDK時的 GoogleService-Info.plist 檔案中尋找一個叫 【REVERSED_CLIENT_ID】的項目,把內容放進來,就好了。

螢幕快照 2018-01-31 上午3.14.26.png

終於,我們可以開始寫 Code了,我們下一篇再討論……

 

 

 

 

iOS Auth(1)匿名認證

認證系統是 APP 安全化的第一部,即使沒有帳號需求,存取資料時,看看是不是由我自己寫的應用程式來存取,也是一件很重要的事。

例如說 Firebase Realtime Database 的存取就是一例,在大多數的相關課程中,都是叫學生把權限設為任意存取,然後千交待萬交待要實驗完把它關閉,並且在實際的產生千萬不要這樣作。但實際的情況是有千千萬萬個開發者為了方便先開啟,然後因為忘了或懶墮等原因,沒有把它關起來,所以我決定,先教大家做一個最最小的認證方式。

首先,第一步要引入 Firebase SDK,我假設你己經完成了這個動作了,若還沒有,請參考下面的文章,建立一個完成引入SDK的新專案:

iOS 引入 Firebase SDK

接下來呢,就要引入 Authentication 部份的 SDK 了,其實方法是一樣的,在 Podfile 中,加上 Authentication 的部份,然後重新執行 pod install 就可以了,如下圖

PodFile:

螢幕快照 2018-01-29 上午12.09.20.png

Pod install 執行結果:

螢幕快照 2018-01-29 上午12.10.05

接下來,需要在後台開啟相關功能,請至Firebase 主控台,並選取專案的 Authentication 功能,開匿名認證功能

螢幕快照 2018-01-29 上午12.21.37

最後,在進入的頁面加上認證相關的程式碼,就可以了

ViewController.swift

import UIKit
import FirebaseAuth
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        Auth.auth().signInAnonymously { (user, error) in

            if error != nil {
                print(error?.localizedDescription)
            }else{
                print(user.debugDescription)
            }
        }
    }
}

 

首先,必需 import FirebaseAuth 程式庫,然後在 viewDidLoad 方法中,加上登入的程式,以確認在畫面一開始就先確認登入的狀況。在程式中,我們只用到了一個方法,就是Auth.auth().signInAnonymously  這個方法必需處理一個完成事件,由於我們還沒有任何其他動作,所以就先把成功或失敗給印出來。

如果一切正確的話,Xcode 的 output 應該會輸出一個 Optional 的 FIRUser ID

螢幕快照 2018-01-29 上午12.47.10

這個狀態才是完成所有測試前的準備動作,在開始 DB, Storage 等測試之前,都應該先做好至少的匿名登入。