edd*_*uld 5 entity-framework owin asp.net-identity azure-active-directory openid-connect
我正在尝试改进旧版 ASPNet MVC/OWIN 应用程序的身份验证故事 - 目前,它使用 AspNetUsers / AspNetRoles / Claims 等表以及基于表单 + cookie 的身份验证。
我想使用 Azure AD / OpenID Connect 进行身份验证,然后像当前一样从数据库加载用户配置文件/角色。基本上,应用程序内不再有密码管理。用户本身仍然需要在应用程序中存在/创建。
该应用程序非常依赖于与这些用户关联的一些自定义数据,因此简单地使用 Active Directory 中的角色并不是一种选择。
OpenID 身份验证可以工作,但是我不确定如何将现有的 Identityuser / IdentityUserRole / RoleManager 管道与其结合使用。
基本上,一旦用户使用 Open ID 进行身份验证,我们就会希望从数据库加载相应的用户(与电子邮件地址匹配)并继续使用该用户配置文件/角色。
特别是,AuthorizeAttribute(指定了特定角色)应继续像以前一样发挥作用。
这是我到目前为止所拥有的:
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));
}
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
这就是我最终所做的:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
它与我最初的非常相似 - (注意:RoleClaimType = ClaimTypesRoles不是“角色”)除了尝试在回调中处理用户之外SecurityTokenValidated,我添加了一些自定义中间件来查找匹配的用户(通过电子邮件地址)并添加从匹配的应用程序用户到经过身份验证的用户身份(OpenID 身份)的声明(应用程序角色)。
最后,我使用(自定义)AuthorizeAttribute(此处未显示)保护所有控制器操作,以确保经过身份验证的用户至少属于“用户”角色(如果不是,则将它们重定向到“无访问”页面,表明我们已通过身份验证他们,但他们无权访问系统)。
| 归档时间: |
|
| 查看次数: |
2193 次 |
| 最近记录: |