用于 Facebook 和 Google 的 OWIN OAuth2 身份验证
无需 Entityframework 即可用于 Facebook 和 Google 的 OWIN OAuth2 身份验证。
引言
有多少人在使用 OWIN 和实现社交网络登录时遇到很大的困难?
代码
您可以查看我目前正在开发的一个项目 (IProject),其中包含本文中的所有代码。
背景
过去三天我一直在学习 OWIN,并且没有使用 Entityframework
,一开始感觉不可能,我开始放弃。
但是当我让它工作起来后,我发现它实际上非常简单。
我将向您解释一种非常简单且有组织的方式来使用 OWIN 设置 Google、Facebook 和 Cookie 身份验证。
我正在使用 EntityWorker.Core
作为 ORM,您可以使用任何其他 ORM。这真的无关紧要。
Using the Code
我们从您需要哪些包开始
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security.Facebook
Microsoft.Owin.Security.Google
Microsoft.Owin.Security.Cookies
创建两个类,Startup.cs 将位于应用程序的根目录下,Startup.Auth.cs 将位于 app_Start
下,并包含 OWIN 配置。
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(IProduct.Startup))]
namespace IProduct
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
以及 Startup.Auth.cs。
为了使 Google 和 Facebook 应用程序正常工作,您需要为它们创建 clientId
和 appId
。
对于 Google,您可以学习这里了解如何操作。
对于 Facebook,您可以学习这里了解如何操作。
using IProduct.Models.OAuthProviders;
using IProduct.Modules;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using Microsoft.Owin.Security.Facebook;
using Owin;
using System;
namespace IProduct
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var googleCredentials = Actions.LoadCredentials(SignInApplication.Google);
var facebookCredentials = Actions.LoadCredentials(SignInApplication.Facebook);
if(googleCredentials == null)
throw new Exception
("GoogleCredentials could not be found(GoogleOAuth2Authentication)");
if(facebookCredentials == null)
throw new Exception
("FacebookCredentials could not be found(FacebookAuthenticationOptions)");
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
var cookieOptions = new CookieAuthenticationOptions
{
LoginPath = new PathString("/Account/Index"),
SlidingExpiration = true,
Provider = new CookieProvider(),
ExpireTimeSpan = TimeSpan.FromDays(7)
};
app.UseCookieAuthentication(cookieOptions);
var googleOption = new GoogleOAuth2AuthenticationOptions()
{
ClientId = googleCredentials.Client_Id,
ClientSecret = googleCredentials.Client_Secret,
CallbackPath = new PathString("/Google"),
Provider = new GoogleProvider(),
AuthenticationType = googleCredentials.Provider
//SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
};
app.UseGoogleAuthentication(googleOption);
var facebookOptions = new FacebookAuthenticationOptions()
{
AppSecret = facebookCredentials.Client_Secret,
AppId = facebookCredentials.Client_Id,
AuthenticationType = facebookCredentials.Provider,
Provider = new FacebookProvider()
};
app.UseFacebookAuthentication(facebookOptions);
}
}
}
现在让我们创建一个 UserManager.cs,它将包含 ClaimsIdentity
和其他操作的逻辑。
using IProduct.Modules;
using IProduct.Modules.Data;
using IProduct.Modules.Library;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Facebook;
using Microsoft.Owin.Security.Google;
using System;
using System.Linq;
using System.Security.Claims;
using System.Web;
namespace IProduct.Models
{
public class UserManager : IDisposable
{
private DbContext _dbContext = new DbContext();
public void SignIn(SignInApplication type, GenericView<User> user = null)
{
switch(type)
{
case SignInApplication.Cookie:
Create(user);
break;
case SignInApplication.Facebook:
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { IsPersistent = true,
RedirectUri = "Account/Facebook" },
SignInApplication.Facebook.ToString());
break;
case SignInApplication.Google:
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { IsPersistent = true,
RedirectUri = "Account/Google" },
SignInApplication.Google.ToString());
break;
}
}
// normal login
public void Create(GenericView<User> model)
{
var user = _dbContext.Get<User>().Where(x => x.Email.Contains(model.View.Email) &&
x.Password == model.View.Password).LoadChildren().ExecuteFirstOrDefault();
if (user == null)
return;
Authorize(user, model.View.RememberMe);
}
// For Facebook
public void Create(FacebookAuthenticatedContext context)
{
if(string.IsNullOrEmpty(context.Email))
return;
var email = context.Email;
var user = _dbContext.Get<user>().Where(x => x.Email == email).ExecuteFirstOrDefault();
if(user == null) // if User dose not exist in our database then create it
{
user = new User
{
Email = email,
Password = "xxxxxxx", // User have to change it later
Person = new Person()
{
FirstName = context.Name,
LastName = "",
Address = new Address()
{
AddressLine = string.Empty,
Country_Id = _dbContext.Get<country>()
.Where(x => x.CountryCode.Contains("sv-se"))
.ExecuteFirstOrDefault().Id.Value
}
},
Role = _dbContext.Get<role>()
.Where(x => x.RoleType == Roles.Customers)
.ExecuteFirstOrDefault()
};
_dbContext.Save(user).SaveChanges();
}
Authorize(user);
}
// For Google
public void Create(GoogleOAuth2AuthenticatedContext context)
{
if(string.IsNullOrEmpty(context.Email))
return;
var email = context.Email;
var user = _dbContext.Get<user>().Where(x => x.Email == email).ExecuteFirstOrDefault();
if(user == null) // if the user dose not exist in our database then create it
{
user = new User
{
Email = email,
Password = "xxxxxxx", // User have to change it later
Person = new Person()
{
FirstName = context.Name,
LastName = context.FamilyName,
Address = new Address()
{
AddressLine = string.Empty,
Country_Id = _dbContext.Get<country>()
.Where(x => x.CountryCode.Contains("sv-se"))
.ExecuteFirstOrDefault().Id.Value
}
},
Role = _dbContext.Get<role>()
.Where(x => x.RoleType == Roles.Customers).ExecuteFirstOrDefault()
};
_dbContext.Save(user).SaveChanges();
}
Authorize(user);
}
private void Authorize(User user, bool isPersistent = true)
{
var ident = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.NameIdentifier, user.Email),
new Claim
("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
"ASP.NET Identity",
"http://www.w3.org/2001/XMLSchema#string"),
new Claim(ClaimTypes.Name, user.Person.FullName),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Role.Name)
}, CookieAuthenticationDefaults.AuthenticationType);
/// write to Cookie
HttpContext.Current.GetOwinContext()
.Authentication.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, ident);
}
public void SignOut()
{
HttpContext.Current.GetOwinContext()
.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
}
public User GetCurrentUser()
{
var email = HttpContext.Current.GetOwinContext()
.Authentication.User.Claims.
FirstOrDefault(x => x.Type == "email" || x.Type == ClaimTypes.Email)?.Value;
if(string.IsNullOrEmpty(email))
return null;
var user = _dbContext.Get<user>()
.Where(x => x.Email == email).LoadChildren()
.IgnoreChildren(x => x.Invoices).ExecuteFirstOrDefault();
return user;
}
public void Dispose()
{
_dbContext.Dispose();
}
}
}
现在我们创建了 UserManager
,我们需要创建另外两个类。这些是提供程序,FacebookProvider
和 GoogleProvider
。
不幸的是,Owin 不会自动注册 ClaimTypes
,我们需要从这两个提供程序中获取信息,并自己将其添加到 OWIN 中。
或者,如果您愿意,您可以将委托添加到 Startup.Auth
类中的 OnAuthenticated
中,我选择这种方式是为了将来添加代码。
using Microsoft.Owin.Security.Google;
using System.Threading.Tasks;
namespace IProduct.Models.OAuthProviders
{
public class GoogleProvider : GoogleOAuth2AuthenticationProvider
{
public override Task Authenticated(GoogleOAuth2AuthenticatedContext context)
{
using(var m = new UserManager())
m.Create(context);
return base.Authenticated(context);
}
}
}
using Microsoft.Owin.Security.Facebook;
using System.Threading.Tasks;
namespace IProduct.Models.OAuthProviders
{
public class FacebookProvider : FacebookAuthenticationProvider
{
public override Task Authenticated(FacebookAuthenticatedContext context)
{
using(var m = new UserManager())
m.Create(context);
return base.Authenticated(context);
}
}
}
好吧,这就是我们需要的 OWIN 实现的所有内容。现在让我们看看如何通过控制器调用这些提供程序。
我将创建一个 AccountController
,其中包含两个用于 Facebook 和 Google 回调的方法,以及一个用于登录的方法。
using EntityWorker.Core.Helper;
using IProduct.Controllers.Shared;
using IProduct.Models;
using IProduct.Modules;
using System.Web.Mvc;
namespace IProduct.Controllers
{
public class AccountController : SharedController
{
[AllowAnonymous]
public ActionResult Index(GenericView<User> user, string type = "")
{
if (Request.IsAuthenticated)
return Redirect("~/Home");
else if (type.ConvertValue<SignInApplication?>().HasValue)
{
using (var manager = new UserManager())
{
if (!Request.IsAuthenticated)
{
manager.SignIn(type.ConvertValue<SignInApplication>(), user);
}
}
if (type.ConvertValue<SignInApplication>() == SignInApplication.Cookie &&
!Request.IsAuthenticated)
return View(user.Error("Email or Password could not be found."));
}
return View(user ?? new GenericView<User>());
}
#region Google
// we may need to add some changes here later as if now, the Google provider
// take care of the login
[AllowAnonymous]
public ActionResult Google(string error)
{
if(Request.IsAuthenticated)
return Redirect("~/Home");
return Redirect("Index");
}
#endregion
#region Facebook
// we may need to add some changes here later as if now, the Facebook provider
// take care of the login
[AllowAnonymous]
public ActionResult Facebook(string error)
{
if(Request.IsAuthenticated)
return Redirect("~/Home");
return Redirect("Index");
}
#endregion
}
}
如果您有多个角色,您可能需要指定哪些角色有权访问哪些控制器。在这种情况下,您需要创建并继承自 AuthorizeAttribute
using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
namespace IProduct.Models
{
public class PAuthorize : AuthorizeAttribute
{
public PAuthorize()
{
base.Roles = IProduct.Modules.Roles.Customers.ToString();
}
public PAuthorize(params IProduct.Modules.Roles[] roles)
{
if (roles != null && roles.Any())
base.Roles = string.Join(",", roles.Select(x => x.ToString()));
else
base.Roles = IProduct.Modules.Roles.Customers.ToString();
}
protected override void HandleUnauthorizedRequest(AuthorizationContext ctx)
{
if (!ctx.HttpContext.User.Identity.IsAuthenticated)
base.HandleUnauthorizedRequest(ctx);
else
{
var role = HttpContext.Current.GetOwinContext().Authentication.User.Claims
.FirstOrDefault(x => x.Type == "role" || x.Type == ClaimTypes.Role)?.Value;
// Role check IsAuthenticated
if (!(role == IProduct.Modules.Roles.Administrator.ToString() || Roles.Contains(role)))
{
ctx.Result = new ViewResult { ViewName = "Unauthorized" };
ctx.HttpContext.Response.StatusCode = 403;
}
}
}
}
}
现在剩下的就是 HTML 和一点 Jquery。
@model IProduct.Modules.Library.Custom.GenericView<IProduct.Modules.Library.User>
@{
Layout = "";
}
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")
<div class="login">
<div class="externalLogin view">
<div class="viewData">
<button href='@Url.Action("SignIn", new {type= "Facebook"})'
class="loginBtn loginBtn--facebook">
Login with Facebook
</button>
<button href='@Url.Action
("SignIn", new {type= "Google"})' class="loginBtn loginBtn--google">
Login with Google
</button>
</div>
</div>
<div class="seperator">
<div class="text">OR</div>
</div>
@using (Html.BeginForm("Index", "Account",
new { type = IProduct.Modules.SignInApplication.Cookie.ToString() }, FormMethod.Post, null))
{
<div class=" view">
<div class="viewData">
<div class="form-group">
@Html.TextBoxFor(x => x.View.Email,
new { @class = "form-control", placeholder = "Email", id="email-sign-in" })
</div>
<div class="form-group">
@Html.PasswordFor(x => x.View.Password,
new { @class = "form-control", placeholder = "Password", id = "password-sign-in" })
</div>
<div class="form-group">
@Html.CheckBoxFor(x => x.View.RememberMe,
new { label = "Remember Me", boxClass = "float-left" })
<a class="float-right">Forgot Password?</a>
</div>
<div class="form-group">
<button type="submit" class="m-button loginBtn--signin">
Sign In
</button>
<button class="m-button loginBtn--signup">
Sign Up
</button>
</div>
</div>
</div>
}
@Html.Action("SignUp", new { user = "" })
@{
if (!Model.Success && Model.GetHtmlError() != null)
{
<div class="error">
@Html.Raw(Model.GetHtmlError())
</div>
}
}
</div>
<script>
var signupDialog = $("body").dialog(
{
data: $(".signupForm"),
title: "Sign Up",
removable: false
});
$(".loginBtn--signup").click(function ()
{
signupDialog.Show();
return false;
});
/// make a button act like a link
function toLocation(item)
{
$(item).each(function ()
{
var location = $(this).attr("href");
if(location && location !== "")
$(this).click(function ()
{
window.location.href = location;
});
});
}
$(document).ready(function ()
{
$(".login input[type=checkbox]").checkBox();
$(window).resize(function ()
{
$(".login").center();
});
$(".login").center();
toLocation($(".m-button"));
});
</script>
关注点
我真的希望这能帮助到大家,最后,感谢您阅读我的代码。
历史
- 2018年10月15日:初始版本