65.9K
CodeProject 正在变化。 阅读更多。
Home

用于 Facebook 和 Google 的 OWIN OAuth2 身份验证

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (11投票s)

2018年10月15日

CPOL

2分钟阅读

viewsIcon

22181

无需 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 应用程序正常工作,您需要为它们创建 clientIdappId

对于 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,我们需要创建另外两个类。这些是提供程序,FacebookProviderGoogleProvider

不幸的是,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日:初始版本
© . All rights reserved.