如何在单个 Microsoft ASP.NET MVC Web 应用程序中同时支持个人用户帐户和工作或学校帐户?
Posted
技术标签:
【中文标题】如何在单个 Microsoft ASP.NET MVC Web 应用程序中同时支持个人用户帐户和工作或学校帐户?【英文标题】:How can I support both Individual User Accounts and Work or School Accounts in a single Microsoft ASP.NET MVC web application? 【发布时间】:2019-07-09 18:51:05 【问题描述】:我想创建一个 ASP.NET MVC Web 应用程序,其中用户身份验证机制基于用户类型。因此,如果单击个人帐户,他将被重定向到应用程序的登录页面。如果他单击公司帐户,那么他将被重定向到 Azure AD 登录页面。实际上,该应用程序将支持基于表单的 ASP.NET Identity 身份验证和多租户 Azure AD 身份验证。
在单个 ASP.NET MVC Web 应用程序中实现此类功能的最佳方式是什么? 具体来说,我需要对 Startup 类中的中间件代码进行哪些更改?
ASP.NET MVC 版本:5.2
.NET 框架:4.7
Visual Studio IDE:2017 社区
作为参考,如果我在创建全新的 Web 应用程序时选择个人用户帐户模板,我会得到以下代码:
// For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
//
// ClientId = "",
// ClientSecret = ""
//);
如果我选择Azure AD登录(多个组织),以下是代码:
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private string graphResourceID = "https://graph.windows.net";
private static string aadInstance = EnsureTrailingSlash(ConfigurationManager.AppSettings["ida:AADInstance"]);
private string authority = aadInstance + "common";
private ApplicationDbContext db = new ApplicationDbContext();
public void ConfigureAuth(IAppBuilder app)
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions );
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
ClientId = clientId,
Authority = authority,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
// instead of using the default validation (validating against a single issuer value, as we do in line of business apps),
// we inject our own multitenant validation logic
ValidateIssuer = false,
,
Notifications = new OpenIdConnectAuthenticationNotifications()
SecurityTokenValidated = (context) =>
return Task.FromResult(0);
,
AuthorizationCodeReceived = (context) =>
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCodeAsync(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceID).Result;
return Task.FromResult(0);
,
AuthenticationFailed = (context) =>
context.OwinContext.Response.Redirect("/Home/Error");
context.HandleResponse(); // Suppress the exception
return Task.FromResult(0);
);
private static string EnsureTrailingSlash(string value)
if (value == null)
value = string.Empty;
if (!value.EndsWith("/", StringComparison.Ordinal))
return value + "/";
return value;
【问题讨论】:
您将不得不实现自己的SignInManager
、UserManager
等。不难,接口就在那里。
查看多租户 Azure AD 应用程序 - docs.microsoft.com/en-us/azure/active-directory/develop/…
具体来说,我需要对 Startup 类中的中间件代码进行哪些更改以适应基于某种请求 URL 模式的两种身份验证机制?
【参考方案1】:
假设您有一个至少名为 Home 的控制器或任何您需要使用 owin 中间件的控制器。
如果使用个人帐户访问应用程序,将使用以下操作。
-
登录(使用本地或任何类型的
身份验证)
注销(从应用程序中注销)
我不打算详细介绍登录和注销,因为我认为您已经实现了它们。
如果想从任何学校或组织帐户登录,您也必须具备这些操作
public void SignIn()
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties RedirectUri = "/" , OpenIdConnectAuthenticationDefaults.AuthenticationType);
public void SignOut()
// Send an OpenID Connect sign-out request.
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
public void EndSession()
// If AAD sends a single sign-out message to the app, end the user's session, but don't redirect to AAD for sign out.
HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
public void UserNotBelongToSystem()
// If AAD sends a single sign-out message to the app, end the user's session, but don't redirect to AAD for sign out.
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
然后是启动类
public partial class Startup
以下是使用的命名空间。
using Castle.MicroKernel.Registration;
using Microsoft.IdentityModel.Protocols;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Runtime.Serialization;
using System.Security.Claims;
启动中有一个方法为
private static Task RedirectToIdentityProvider(Microsoft.Owin.Security.Notifications.RedirectToIdentityProviderNotification<Microsoft.IdentityModel.Protocols.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> arg)
string appBaseUrl = arg.Request.Scheme + "://" + arg.Request.Host + arg.Request.PathBase;
arg.ProtocolMessage.RedirectUri = appBaseUrl + "/";
arg.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
arg.ProtocolMessage.Prompt = "login";
if (arg.ProtocolMessage.State != null)
var stateQueryString = arg.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = arg.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("mycustomparameter", UtilityFunctions.Encrypt("myvalue"));
arg.ProtocolMessage.State = stateQueryString[0] + "=" + arg.Options.StateDataFormat.Protect(state);
return Task.FromResult(0);
通过上述操作,您将自定义值发送给 azure authehtication 提供程序,表明此请求最初是由您生成的。
因为一旦您收到响应,您需要验证是您将请求重定向到身份提供者进行身份验证
private static Task OnMessageReceived(Microsoft.Owin.Security.Notifications.MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
if (notification.ProtocolMessage.State != null)
string mycustomparameter;
var protectedState = notification.ProtocolMessage.State.Split('=')[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.TryGetValue("mycustomparameter", out mycustomparameter);
if (UtilityFunctions.Decrypt(mycustomparameter) != "myvalue")
throw new System.IdentityModel.Tokens.SecurityTokenInvalidIssuerException();
return Task.FromResult(0);
以下是在启动时令牌过期的情况下注销或处理用户的方法
private Task AuthenticationFailed(Microsoft.Owin.Security.Notifications.AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
//context.HandleResponse();
//context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
private Task SecurityTokenValidated(Microsoft.Owin.Security.Notifications.SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
string userID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
return Task.FromResult(0);
之后,一旦您在 OnMessageReceived
方法中验证了响应,声明就会添加到身份验证令牌中
private Task AuthorizationCodeReceived(Microsoft.Owin.Security.Notifications.AuthorizationCodeReceivedNotification context)
var code = context.Code;
string userID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
var _objUser = Users.CheckIfTheUserExistInOurSystem(userID);//check this in system again your choice depends upone the requiement you have
if (_objUser == null)
context.HandleResponse();
// context.OwinContext.Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
context.Response.Redirect("Home/UserNotBelongToSystem");// same mehthod added above as to signout
// throw new System.IdentityModel.Tokens.SecurityTokenValidationException();
else
_objUser.IsAZureAD = true;// setting true to find out at the time of logout where to redirect
var claims = Users.GetCurrentUserAllClaims(_objUser);//You can create your claims any way you want just getting from other method. and same was used in case of the normal login
context.AuthenticationTicket.Identity.AddClaims(claims);
context.OwinContext.Authentication.SignIn(context.AuthenticationTicket.Identity);
context.HandleResponse();
context.Response.Redirect("Home/Main");
最后但同样重要的是,您可以检查用户声明以查看当前登录的用户是否来自 Azure,以便重定向到特定的注销页面
if (CurrentUser.IsAZureAD) LogoutUrl = Url.Content("~/Home/SignOut"); else LogoutUrl = Url.Content("~/Home/LogOut");
CurrentUser.IsAZureAD
将无法使用,除非您开设这样的课程
public class CurrentUser: ClaimsPrincipal
public CurrentUser(ClaimsPrincipal principal)
: base(principal)
public string Name
get
// return this.FindFirst(ClaimTypes.Name).Value;
return this.FindFirst("USER_NAME").Value;
public bool IsAZureAD
get
return Convert.ToBoolean(this.FindFirst("IsAZureAD").Value);
【讨论】:
以上是关于如何在单个 Microsoft ASP.NET MVC Web 应用程序中同时支持个人用户帐户和工作或学校帐户?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 ASP.Net MVC4 Web API 项目中使用 Microsoft OCR 库 ( Microsoft.Windows.Ocr )?
使用默认的 ASP.Net MVC 设置,如何使用 Microsoft 帐户登录?
如何使用 Microsoft 身份平台身份验证在 ASP.NET Core Web 应用程序中获取 JWT 令牌?