如何使用 Azure AD/OpenId 进行身份验证,但使用基于实体框架的用户/角色数据 [英] How to authenticate with Azure AD / OpenId but use Entity Framework based user/role data

查看:19
本文介绍了如何使用 Azure AD/OpenId 进行身份验证,但使用基于实体框架的用户/角色数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试改进旧 ASPNet MVC/OWIN 应用程序的身份验证故事 - 目前,它使用 AspNetUsers/AspNetRoles/声明等表以及基于表单 + cookie 的身份验证.

I'm trying to improve the authentication story for a legacy ASPNet MVC/OWIN app - Currently, it uses the AspNetUsers / AspNetRoles / claims etc tables along with forms + cookie based authentication.

我想使用 Azure AD/OpenID Connect 进行身份验证,然后像当前一样从数据库中加载用户配置文件/角色.基本上,应用程序内不再有密码管理.用户本身仍然需要在应用中存在/被创建.

I want to use Azure AD / OpenID Connect for authentication but then load the user profile/roles from the database as currently. Basically, no more password management within the app. Users themselves will still need to exist/be created within the app.

该应用程序非常依赖与这些用户关联的一些自定义数据,因此不能简单地使用 Active Directory 中的角色.

The application is quite dependent on some custom data associated with these users so simply using the roles from Active Directory isn't an option.

OpenID 身份验证有效,但我不确定如何结合使用现有的 Identityuser/IdentityUserRole/RoleManager 管道.

The OpenID auth works, however I'm not sure how to use the existing Identityuser / IdentityUserRole / RoleManager plumbing in conjunction with it.

基本上,一旦用户使用 Open ID 进行身份验证,我们就会希望从数据库中加载相应的用户(匹配电子邮件地址)并使用该用户配置文件/角色.

Basically once the user authenticates with Open ID we'll want to load the corresponding user from the database (matching on email address) and use that user profile / roles going forward.

特别是,AuthorizeAttribute(指定了特定角色)应该继续像以前一样工作.

In particular, the AuthorizeAttribute (with specific roles specified) should continue to function as before.

这是我目前所拥有的:

public class IdentityConfig
{
    public void Configuration(IAppBuilder app)
    {
        app.CreatePerOwinContext(AppIdentityDbContext.Create);
        app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
        app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

        ConfigureAuth(app);
    }

    /// <summary>
    /// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
    /// </summary>
    /// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
    private void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        //Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = ConfigHelper.ClientId,
                Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance,
                    ConfigHelper.Tenant), // For Single-Tenant
                PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,

                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {
                    RoleClaimType = "roles",
                },

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthenticationFailed = context =>
                    {
                        context.HandleResponse();
                        context.Response.Redirect("/Error/OtherError?errorDescription=" +
                                                  context.Exception.Message);
                        return Task.FromResult(0);
                    },
                    SecurityTokenValidated = async context =>
                    {
                        string userIdentityName = context.AuthenticationTicket.Identity.Name;
                        var userManager = context.OwinContext.GetUserManager<AppUserManager>();
                        var user = userManager.FindByEmail(userIdentityName);
                        if (user == null)
                        {
                            Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
                            context.HandleResponse();
                            context.Response.Redirect("/Error/NoAccess?identity=" + userIdentityName);
                            return;
                        }

                        user.DateLastLogin = DateTime.Now;
                        IdentityResult result = await userManager.UpdateAsync(user);

                        if (result.Succeeded)
                        {
                            var authManager = context.OwinContext.Authentication;
                            ClaimsIdentity ident = await userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalBearer);


                            // Attach additional claims from DB user
                            authManager.User.AddIdentity(ident);

                            // authManager.SignOut();
                            // authManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);

                            return;
                        }

                        throw new Exception(string.Format("Failed to update user {0} after log-in", userIdentityName));
                    }
                }
            });
    }
}

推荐答案

这是我最终做的:

public class IdentityConfig
{
    public void Configuration(IAppBuilder app)
    {
        app.CreatePerOwinContext(AppIdentityDbContext.Create);
        app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
        app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
        ConfigureAuth(app);
    }

    /// <summary>
    /// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
    /// </summary>
    /// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
    private void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            CookieDomain = ConfigHelper.AuthCookieDomain,
            SlidingExpiration = true,
            ExpireTimeSpan = TimeSpan.FromHours(2)
        });

        //Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = ConfigHelper.ClientId,
                Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance, ConfigHelper.Tenant),

                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {
                    RoleClaimType = ClaimTypes.Role
                },

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthenticationFailed = context =>
                    {
                        context.HandleResponse();
                        context.Response.Redirect("/Error/OtherError?errorDescription=" + context.Exception.Message);
                        return Task.FromResult(0);
                    },
                    RedirectToIdentityProvider = context =>
                    {
                        // Set the post-logout & redirect URI dynamically depending on the incoming request.
                        // That allows us to use the same Azure AD app for two subdomains (these two domains give different app behaviour)
                        var builder = new UriBuilder(context.Request.Uri);
                        builder.Fragment = builder.Path = builder.Query = "";
                        context.ProtocolMessage.PostLogoutRedirectUri = builder.ToString();
                        context.ProtocolMessage.RedirectUri = builder.ToString();
                        return Task.FromResult(0);
                    }
                }
            });

        app.Use<EnrichIdentityWithAppUserClaims>();
    }
}

public class EnrichIdentityWithAppUserClaims : OwinMiddleware
{
    public EnrichIdentityWithAppUserClaims(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        await MaybeEnrichIdentity(context);
        await Next.Invoke(context);
    }

    private async Task MaybeEnrichIdentity(IOwinContext context)
    {
        ClaimsIdentity openIdUserIdentity = (ClaimsIdentity)context.Authentication.User.Identity;
        string userIdentityName = openIdUserIdentity.Name;

        var userManager = context.GetUserManager<AppUserManager>();
        var appUser = userManager.FindByEmail(userIdentityName);

        if (appUser == null)
        {
            Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
            return;
        }

        appUser.DateLastLogin = DateTime.Now;
        IdentityResult result = await userManager.UpdateAsync(appUser);
        if (result.Succeeded)
        {
            ClaimsIdentity appUserIdentity = await userManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ExternalBearer);
            openIdUserIdentity.AddClaims(appUserIdentity.Claims);
        }
    }
}

它与我最初的非常相似 - (注意:RoleClaimType = ClaimTypesRoles 不是角色")除了尝试在 SecurityTokenValidated 回调中处理用户,我添加了一些自定义中间件,用于查找匹配用户(通过电子邮件地址)并将匹配应用用户的声明(应用角色)添加到经过身份验证的用户身份(OpenID 身份).

It's quite similar to what I had originally - (note: RoleClaimType = ClaimTypesRoles not "roles") except instead of trying to deal with the user in the SecurityTokenValidated callback, I've added some custom middleware that finds a matching user (by email address) and adds the claims (app roles) from the matching app user to the authenticated user identity (OpenID identity).

最后,我使用(自定义)AuthorizeAttribute(此处未显示)保护所有控制器操作,以确保经过身份验证的用户至少属于用户"角色(如果不是,则将它们重定向到无法访问"页面表明我们已经对他们进行了身份验证,但他们在系统中无权访问).

Finally, I protected all controller actions with a (custom) AuthorizeAttribute (not shown here) that ensures the authenticated user at least belongs to the "User" role (if not, redirects them to a "no access" page indicating that we've authenticated them but they have no access in the system).

这篇关于如何使用 Azure AD/OpenId 进行身份验证,但使用基于实体框架的用户/角色数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆