ASP.NET MVC 标识 - 一起使用内部用户和 Azure AD 身份验证

par*_*ani 6 c# asp.net-mvc azure asp.net-identity azure-active-directory

我们已经运行了 ASP.NET MVC Web 应用程序,它通过令牌身份验证使用内部用户。这是以 ASP.NET MVC 模板提供的标准方式实现的。

现在我们需要扩展此身份验证模型并允许外部 Azure AD 用户登录到已配置租户的 Web 应用程序。我已经弄清楚了 Azure AD 方面的所有内容。感谢这里的microsoft github 示例

现在个人帐户身份验证Azure AD独立运行良好。但它不能一起工作。当我将两个中间件插入在一起时,它会出现问题。

这是我的 startup_auth.cs 文件。

public partial class Startup
    {

        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);


            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });            
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);


            string ClientId = ConfigurationManager.AppSettings["ida:ClientID"];            
            string Authority = "https://login.microsoftonline.com/common/";

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = ClientId,
                Authority = Authority,
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {                        
                    ValidateIssuer = false,
                },
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    RedirectToIdentityProvider = (context) =>
                    {                            
                        string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;                         
                        context.ProtocolMessage.RedirectUri = appBaseUrl;
                        context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
                        return Task.FromResult(0);
                    },                        
                    SecurityTokenValidated = (context) =>
                    {
                        // retriever caller data from the incoming principal
                        string issuer = context.AuthenticationTicket.Identity.FindFirst("iss").Value;
                        string UPN = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
                        string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;

                        if (
                            // the caller comes from an admin-consented, recorded issuer
                            (db.Tenants.FirstOrDefault(a => ((a.IssValue == issuer) && (a.AdminConsented))) == null)
                            // the caller is recorded in the db of users who went through the individual onboardoing
                            && (db.Users.FirstOrDefault(b =>((b.UPN == UPN) && (b.TenantID == tenantID))) == null)
                            )
                            // the caller was neither from a trusted issuer or a registered user - throw to block the authentication flow
                            throw new SecurityTokenValidationException();                            
                        return Task.FromResult(0);
                    },
                    AuthenticationFailed = (context) =>
                    {
                        context.OwinContext.Response.Redirect("/Home/Error?message=" + context.Exception.Message);
                        context.HandleResponse(); // Suppress the exception
                        return Task.FromResult(0);
                    }
                }
            });

        }
    }
Run Code Online (Sandbox Code Playgroud)

此配置适用于本地用户帐户,但不适用于 AAD。要启用 AAD 身份验证,我需要配置 UseCookieAuthentication 部分,如下所示。这将破坏我的本地用户帐户身份验证。

app.UseCookieAuthentication(new CookieAuthenticationOptions { });

基本上我需要删除本地用户的中间件才能使 AAD 工作。

我所说的 AAD 不起作用的意思是,我无法进行任何受 [Authoroze] 属性保护的安全操作。它的调用事件 SecurityTokenValidated 并且我能够获得所有 AAD 声明并能够针对我的自定义租户进行验证。但只有在最后我重定向到我的应用程序的根目录时,这是安全操作,它才会返回到我的自定义登录页面。似乎它不是内部登录用户,也没有创建必要的身份验证 cookie。

我将不胜感激关于我在这里可能会遗漏的任何想法。

谢谢

Fei*_*SFT 2

要同时支持个人帐户和社交数据提供商的其他帐户,只需使用 OWIN 组件添加它们。

要注销从 Azure AD 登录的用户,我们需要注销从 Web 应用程序和 Azure AD 发出的 cookie。首先,我修改了该类ApplicationUser以添加自定义声明,以检测用户是从 Azure AD 还是个人帐户登录,如下所示。

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        if((this.Logins as System.Collections.Generic.List<IdentityUserLogin>).Count>0)
            userIdentity.AddClaim(new Claim("idp", (this.Logins as System.Collections.Generic.List<IdentityUserLogin>)[0].LoginProvider));
        return userIdentity;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们可以更改LogOff支持从 Azure AD 注销的方法,以清除 Azure AD 中的 cookie:

// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{

    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);

    var idpClaim = ClaimsPrincipal.Current.Claims.FirstOrDefault(claim => { return claim.Type == "idp"; });
    if (idpClaim!=null)
        HttpContext.GetOwinContext().Authentication.SignOut(
            OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);

    return RedirectToAction("Index", "Home");
}
Run Code Online (Sandbox Code Playgroud)