[ASP.net MVC 4] 使用微軟內建的facebook OAuth登入範本的補充資訊,以瘋狂賣客網站為例
前言
facebook整合網站的登入功能,玩了一下
只是把自家網站登入權交給facebook,使用者有在facebook登入過的話,在自家網站點一下登入鈕就會自行登入
使用者登出facebook的話,在自家網站點了登入鈕後還要再輸入一次facebook的帳號和密碼,才會進行登入
整個流程大概是這樣(以瘋狂賣客網站為例)
Step 1使用者點擊facebook登入鈕(無須輸入左方的帳密)
Step 2 頁面轉到facebook網站,facebook網站發現使用者尚未登入則要求先登入(facebook網站判斷Cookie有無逾期),否則跳到第3步驟
Step 3 facebook使用者第一次接觸瘋狂賣客的App,使用者點擊「確定」的話 ,App授權成功,使用者點擊「取消」的話,App授權失敗
Step 4 承上,使用者點擊「確定」後,頁面轉回瘋狂賣客首頁
若點擊「取消」的話,畫面也是轉回首頁,使用者當做登入失敗
Step 5.承Step 3當使用者點擊「確定」後,使用者的facebook應用程式中心,會出現瘋狂賣客的App
,Step 3 就是依據應用程式中心有無該App來判斷要不要出現詢問授權的畫面
Step 6.當瘋狂賣客 App取得使用者授權後,瘋狂賣客網站還會抓取FB使用者資料至他們的會員資料表
※以下用一般會員登入原本有「修改密碼」欄位,因為我是用FB登入來創建此筆會員資料,所以才沒出現,我已經用「忘記密碼」功能,來確認只要知道密碼,FB登入者也有機會透過一般會員登入方式進入網站,換句話說,證明FB登入和一般會員登入用的會員資料表是共用同一張Table,科科
到目前為止會員資料表大概可推敲有以下欄位,FB登入會員和一般會員共用同一張表
(
MemberID bigint identity primary key,--當PK用
Account nvarchar(max) not null default(''),
Pwd nvarchar(max) not null default(''),
Name nvarchar(max) not null default(''),
email nvarchar(max) not null default(''),
FacebookUserID nvarchar(max) not null default('') --有值的話,表示此筆為FB登入創建的資料
)
Step 7 使用者下次到瘋狂賣客做facebook帳號登入時,這時候使用者已經登入過facebook網站(電腦有facebook Cookie),也授權瘋狂賣客的App存取
再點一次登入鈕,就會直接登入
只是以瘋狂賣客網站為例,使用者登入後,他們還會另寫一份Crazy Mike的Cookie到使用者電腦,用來下次自動登入瘋狂賣客網站的判斷
這部份跟facebook帳號登入整合沒什麼關係就不詳談了
實作
知道流程後,可以開始動工了
一開始我本來使用網路上別人寫好的FacebookScopedClient類別:OAuthWebSecurity With Facebook not using email permission as expected
發現,本機localhost測試功能正常,一旦部署至正式機(有正式DomainName)就會掛掉(發生Null Exception)
後來改拿微軟Visual Studio 內建的facebook登入範本來改,其實就可以了(也支援localhost本地端的測試)
詳見:Using OAuth Providers with MVC 4
建立範本前,注意自己的網站是跑什麼.net版本就建立該版本的網際網路應用程式範本
建好後,到範本網站的Bin資料夾底下找以下的.dll
自己的網站就加入以上那些.dll的參考
然後範本網站的App_Start資料夾下有一個AuthConfig.cs檔,也把它複製到自己的網站中的App_Start資料夾下
接著就可以為自己的網站開始動手寫程式加入facebook登入功能
Global.asax.cs
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
namespace yourNamespace
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
/*Global.asax.cs加這個*/
AuthConfig.RegisterAuth();
}
}
}
AuthConfig.cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
//引用這段
using Microsoft.Web.WebPages.OAuth;
using System.Configuration;
namespace yourNamespace
{
public static class AuthConfig
{
public static void RegisterAuth()
{
//appId和appSecret的產生請見http://www.asp.net/mvc/tutorials/security/using-oauth-providers-with-mvc
OAuthWebSecurity.RegisterFacebookClient(
appId: "",
appSecret: "");
}
}
}
接著View準備一個submit按鈕,用來Click登入,Controller完整代碼在最底下
預設FB會回傳7個欄位資料,如果想要抓額外資料的話
要注意符合以下幾點:
1.facebook app在Graph API Explorer中有設定相對應的權限(例如想要抓使用者的感情狀態就勾選user_relationships)
下圖是facebook app預設的權限
要怎麼到這個畫面,之前文章有寫過,墮性發作,偷懶不寫
請自行到這邊[C#/Facebook API] 利用Facebook API 發文,適合前後端平台(使用者無需輸入帳密方式)從「接著要設定允許此應用程式的權限」這行開始看
2.該欄位(感情狀態欄)使用者有填寫,(隱私設定 公開or本人 不管)
3.使用者有同意該facebook app的存取
4.自家網站要求使用者資料時,有帶access token和該欄位名稱
承上面的範例程式碼,所以要求額外欄位資料時
那個fields有哪些值,可以到Graph API Explorer查看即可。
要抓多個欄位就用逗號分隔:fields=relationship_status,address,about
※2013.8.2 追記
今天專案碰上一個需求
使用者原先要進入A頁,但A頁須使用者先進行登入才可以進去,所以程式把使用者導到B頁(做FB登入),然後要讓使用者做完FB登入後
將使用者導回A頁
解決辦法,呼叫callback action時給予returnUrl值,但在IE可以,Google Chrome的話,該值會是NULL,所以…↓
在A頁導到B頁時,傳一個QueryString,假設叫myUrl,它是http開頭A頁的絕對路徑
所以在B頁瀏覽器Url會看到如下:
http://www.domain.com/Login?myUrl=http://www.domain.com/A
然後是FB按鈕post的Action
然後在FB登入的Action方法ExternalLoginCallback
裡面,判斷使用者登入成功後
string myUrl = "";
//ie的話取returnUrl
if (!string.IsNullOrEmpty(returnUrl))
{
Uri uri = new Uri(returnUrl);
myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
}
else
{//Google chrome
Uri uri = new Uri(Request.UrlReferrer.OriginalString);
myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
}
if (string.IsNullOrEmpty(myUrl))
{
//導至首頁
return RedirectToAction("Index", "Home");
}
else
{//先前Request的A頁
Response.Redirect(myUrl);
}
完整Controller代碼:
internal class ExternalLoginResult : ActionResult
{
public ExternalLoginResult(string provider, string returnUrl)
{
Provider = provider;
ReturnUrl = returnUrl;
}
public string Provider { get; private set; }
public string ReturnUrl { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
OAuthWebSecurity.RequestAuthentication(Provider, ReturnUrl);
}
}
/// <summary>
/// FB按鈕Click登入
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
[HttpPost]
public ActionResult FBLogin(FormCollection form)
{
//myUrl是先前Request的Url,為2013.8.2 專案需求加的
ViewData["myUrl"] = form["myUrl"];
return new ExternalLoginResult("Facebook", Url.Action("ExternalLoginCallback", new { ReturnUrl = Request.UrlReferrer.OriginalString }));
}
public ActionResult ExternalLoginCallback(string returnUrl)
{
string myUrl = "";
//ie的話取returnUrl
if (!string.IsNullOrEmpty(returnUrl))
{
Uri uri = new Uri(returnUrl);
myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
}
else
{//Google chrome
Uri uri = new Uri(Request.UrlReferrer.OriginalString);
myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
}
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{//FB登入失敗
//導回會員登入頁
return RedirectToAction("Login","Member");
}
else
{//FB登入成功
//FB回傳的資料
// id,username,name,link,gender,birthday,accesstoken,
string fbID = result.ProviderUserId;
string fbName = result.ExtraData["name"];
string fbEmail = result.ExtraData["username"];
//檢查DB有無此會員資料,依FacebookID(略
IEnumerable<Member> members = _dbContext.Members.Where(m=>m.FacebookID==fbID);
if (!members.Any())
{//DB沒有,建立(略
Member m = new Member();
//塞資料(略
_dbContext.Members.Add(m);
_dbContext.SaveChanges();
}
else
{//DB已有,更新資料...
Member m = members.FirstOrDefault();
m.Name = fbName;
m.Email = fbEmail;
_dbContext.SaveChanges();
}
//取得DB裡的FB會員資料(依FacebookID)
Member member = _dbContext.Members.Where(x => x.FacebookID == fbID).FirstOrDefault();
Session["Member"] = member;//幫User做登入
if (string.IsNullOrEmpty(myUrl))
{
//導至首頁
return RedirectToAction("Index", "Home");
}
else
{//先前Request的Url
Response.Redirect(myUrl);
}
}
//預設導至首頁
return RedirectToAction("Index", "Home");
}
結語
還是自己親手寫過比較了解整個運作