愛流浪的小風

技術隨手寫

[Asp.Net MVC] 使用Asp.Net Idetity整合Google登入

| Comments

隨著網路服務越來越多,現在也有許多網站開始提供透過社交網站來進行登入功能(例如:Google、Twitter、Facebook等等),這樣的好處是不必在自己重新撰寫會員系統,而是可以很快速的透過社交網站驗證使用者是本人之後,再導回我們提供服務的網站,用同一個使用者帳號來進行各種操作。而Asp.Net現在也提供了完整整合OAuth2這種驗證方式的Asp.Net Identity,讓我們快速實現使用OAuth2透過社交網站登入的功能,這篇文章就要向大家分享如何使用Asp.Net Identity來透過Google帳號登入自己的網站。

※本篇內容適合只想整合OAuth驗證,但不想要整合其他Asp.Net Identity會員機制(User、Role),或會員登入要使用自製流程的人

註冊Google Api Key

原本我們使用Asp.Net Identity,不一定需要申請Google Api Key,而是可以透過Google OpenId整合登入的方式,但目前Google因為安全性的考量,將要把使用OpenId登入的功能關閉,也不接受新的網域使用Google OpenId來登入(原本有使用OpenID的也將在2015/4/20關閉,建議盡早修改),所以現在如果要使用Google做登入的話,只能使用OAuth的方式囉!

  1. 進入Google Developer Console - https://console.developers.google.com/project

  2. 新增一個專案,或使用已經擁有的專案

  3. 等待專案建立完成後,進入專案詳細頁面,選擇憑證,點選建立新的用戶端ID

  4. 若還沒有設定產品資訊的話,會提示要先設定同意畫面的資料,需要先將其填寫完畢

  5. 在建立畫面,選擇網路應用程式,填寫網址和callback網址(Asp.Net Identity預設註冊route,擁有signin-google這個callback網址)

  6. 建立完成後,可以看到成功的產生密碼資訊,用戶端ID就是ClientID,用戶端密碼就是ClientSecret

  7. 點選左側的API選項,我們必須要開啟Google+ API,才能在登入時取得登入者的資訊(例如Email)

  8. 啟用Google+ API

  9. 啟用成功後,可以再已啟用的API看到,到這邊就完成了Google Api Key的申請

使用Asp.Net Identity整合登入功能

  1. 建立一個新的Asp.Net Mvc站台,因為要使用Google登入,所以在驗證選項這邊先選擇不驗證

  2. 安裝下列Nuget套件,可以從Nuget套件管理員 => 套件管理主控台以指令方式安裝較快

    Install-Package Microsoft.AspNet.Identity.Core -Version 2.1.0
    Install-Package Microsoft.AspNet.Identity.Owin -Version 2.1.0
    Install-Package Microsoft.Owin.Security.Google -Version 3.0.0
    Install-Package Microsoft.Owin.Security.OAuth -Version 3.0.0
    Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.0.0
    
  3. 在App_Start資料夾下新增Startup.Auth.cs檔案,設定登入方式與Google Api的ClientId、ClientSecret

    App_Start\Startup.Auth.cs
    using System;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using Microsoft.Owin;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.Google;
    using Owin;
    
    namespace WebApplication3
    {
        public partial class Startup
        {
            // 如需設定驗證的詳細資訊,請瀏覽 http://go.microsoft.com/fwlink/?LinkId=301864
                public void ConfigureAuth(IAppBuilder app)
            {
                // 讓應用程式使用 Cookie 儲存已登入使用者的資訊
                    // 並使用 Cookie 暫時儲存使用者利用協力廠商登入提供者登入的相關資訊;
                    // 在 Cookie 中設定簽章
                    app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                    LoginPath = new PathString("/Account/Login"),             
                });
    
                app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    
                // TODO: 輸入Client Id和Client Secret
                    app.UseGoogleAuthentication(
                    clientId: "<YOUR_CLIENTID>",
                    clientSecret: "<YOUR_CLIENTSECRET>");            
            }
        }
    }    
    
  4. 在網站下新增Startup.cs,設定OWIN啟動時執行Startup設定

    Startup.cs
    using System;
    using System.Threading.Tasks;
    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(WebApplication3.Startup))]
    
    namespace WebApplication3
    {
        public partial class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                // 如需如何設定應用程式的詳細資訊,請參閱  http://go.microsoft.com/fwlink/?LinkID=316888'
                    ConfigureAuth(app);
            }
        }
    }
    
  5. 在Controllers新增AccountController,設定外部登入相關Action

    Controllers\AccountController.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Mvc;
    using Microsoft.Owin.Security;
    using System.Security.Claims;
    
    namespace WebApplication3.Controllers
    {
        public class AccountController : Controller
        {        
            private IAuthenticationManager AuthenticationManager
            {
                get
                {
                    return HttpContext.GetOwinContext().Authentication;
                }
            }
    
            //
                // GET: /Account/Login
                [AllowAnonymous]
            public ActionResult Login(string returnUrl)
            {
                ViewBag.ReturnUrl = returnUrl;
                return View();
            }
    
            public ActionResult Logout()
            {
                this.AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
    
                return RedirectToAction("Login");
            }
    
            [HttpPost]
            [AllowAnonymous]
            [ValidateAntiForgeryToken]
            public ActionResult ExternalLogin(string provider, string returnUrl)
            {
                // 要求重新導向至外部登入提供者
                    return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new         { ReturnUrl = returnUrl }));
            }
    
            //
                // GET: /Account/ExternalLoginCallback
                [AllowAnonymous]
            public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
            {
                var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
                if (loginInfo == null)
                {
                    return RedirectToAction("Login");
                }
    
                // 若使用者已經有登入資料,請使用此外部登入提供者登入使用者
                    if (loginInfo != null)
                {
                    var id = new ClaimsIdentity(loginInfo.ExternalIdentity.Claims,
                                                DefaultAuthenticationTypes.ApplicationCookie);
    
                    //// TODO: 加上你的驗證邏輯,或是註冊會員邏輯                
        
                    AuthenticationManager.SignIn(id);
    
                    return RedirectToLocal(returnUrl);
                }
                else
                {
                    return RedirectToAction("Login");
                }
            }
    
            private ActionResult RedirectToLocal(string returnUrl)
            {
                if (Url.IsLocalUrl(returnUrl))
                {
                    return Redirect(returnUrl);
                }
                else
                {
                    return RedirectToAction("Index", "Home");
                }
            }
    
            // 新增外部登入時用來當做 XSRF 保護
                private const string XsrfKey = "XsrfId";
    
            internal class ChallengeResult : HttpUnauthorizedResult
            {
                public ChallengeResult(string provider, string redirectUri)
                    : this(provider, redirectUri, null)
                {
                }
    
                public ChallengeResult(string provider, string redirectUri, string userId)
                {
                    LoginProvider = provider;
                    RedirectUri = redirectUri;
                    UserId = userId;
                }
    
                public string LoginProvider { get; set; }
                public string RedirectUri { get; set; }
                public string UserId { get; set; }
    
                public override void ExecuteResult(ControllerContext context)
                {
                    var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
                    if (UserId != null)
                    {
                        properties.Dictionary[XsrfKey] = UserId;
                    }
                    context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
                }
            }
        }
    }
    
  6. 在Views/Account新增Login.cshtml頁面,提供登入畫面

    Views\Account\Login.cshtml
    @{
        ViewBag.Title = "Login";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    <div class="jumbotron" style="margin-top:15px">
        <h1>Log On</h1>
        <p>Please login to system.</p>
        <p>
            @using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = ViewBag.ReturnUrl }))
            {
                @Html.AntiForgeryToken()
                <button type="submit" class="btn btn-primary" id="Google" name="provider" value="Google" title="Signin with Google account">Signin with Google account</button>
            }
        </p>
    </div>
    
  7. 執行網站,進入/Account/Login,可以使用Google來登入

  8. 點擊後,出現Google的授權畫面

  9. 按下同意,成功的回到首頁

使用NSquared.AspNet.Identity.Google快速整合

除了按照上面的操作說明整合Asp.Net Identity使用Google登入之外,我也將這些步驟包裝為一個Nuget Package,就可以不用手動一個一個新增檔案,而是透過Nuget的方式快速整合Google登入。(使用前請先確認目前網站沒有使用Asp.Net Identity,新增專案時也沒有驗證選項要選擇不驗證,避免要新增的檔案已經存在)

  1. 一樣建立一個新的Asp.Net Mvc站台,一樣選擇不驗證

  2. 安裝Nuget套件,選擇NSquared.AspNet.Identity.Google

    此套件目前為PreRelease版本

  3. 進入App_Start\Startup.Auth.cs,將Google Api的Client Id和Secret更換為剛剛申請的資料

  4. 啟動網站,可以發現我們成功的使用Google登入囉!

  5. 目前這個Nuget Package也是Open Source,歡迎大家發送PullRequest,網址:https://github.com/91mai/NSquared.AspNet.Identity.Google

小結

透過第三方網站提供的OAuth登入整合,提供了一個方便的渠道讓使用者進行註冊或登入,解決使用者需要記憶額外帳號密碼的不方便性,也可以讓使用者透過自己信任的平台來進行登入。Asp.Net Identity的功能相當強大,還有許多的功能可以讓我們對於會員和登入機制的使用更加的方便,未來有機會再陸續分享給大家!

Comments

comments powered by Disqus