我有一个现有的 MVC 项目,它使用 FormsAuthentication 进行身份验证。
除了现有的常规登录页面之外,我还需要添加使用 OpenID IDP 登录的选项。
我遇到的问题是按需挑战 IDP 并在收到声明后设置身份验证 cookie,我找不到 cookie 未粘贴的原因。该流程似乎运行良好,我可以在 AuthorizationCodeReceived 回调中看到声明。
这是 Startup.Auth.cs 代码:
var notificationHandlers = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
string username = context.AuthenticationTicket.Identity.FindFirst("preferred_username").Value;
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(60), true, "");
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
context.Response.Cookies.Append(FormsAuthentication.FormsCookieName, encryptedTicket);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.OwinContext.Request.Path.Value != "/Account/SignInWithOpenId")
{
context.OwinContext.Response.Redirect("/Account/Login");
context.HandleResponse();
}
return Task.FromResult(0);
}
};
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "xxxxxxxxx",
ClientId = "MyClient",
ClientSecret = "xxxxxxxx",
RedirectUri = "http://localhost:52389/",
PostLogoutRedirectUri = "http://localhost:52389/",
ResponseType = "code id_token",
Scope = "openid profile email roles",
UseTokenLifetime = false,
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "preferred_username",
RoleClaimType = "role"
},
Notifications = notificationHandlers
});
app.SetDefaultSignInAsAuthenticationType("Cookies");
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider()
});
app.UseStageMarker(PipelineStage.Authenticate);
这是 AccountController SignInWithOpenId 方法:
public ActionResult SignInWithOpenId()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
// If I don't have this line, reponse redirects to the forms authentication login... so maybe something is wrong here?
return new HttpUnauthorizedResult("IDP");
}
else
{
return RedirectToAction("Index", "Default");
}
}
任何指点将不胜感激。谢谢你。
通过将这些代码添加到 Global.asax 来解决:
protected void Application_BeginRequest()
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
这正是我目前正在尝试做的事情。 如果我发现任何有用的东西,我会告诉你。
更新: 我最终在 MVC Web 应用程序中禁用了表单身份验证。我正在做一个概念验证,所以这不是一个硬性要求。 我知道这不是你真正想要的。 我成功使用 IdP 登录并重定向回 Web 应用程序。概念验证结束的地方是需要填充 HttpContext.User 对象。
我能够在 .NET 4.7 中得到这个,或者至少是等效的。我的用例是,大多数订阅者正在升级为通过 Azure AD B2C 登录,但我们有公共 PC,我们希望通过模糊 URL 进行手动声明进行身份验证。
我正在使用
Microsoft.Owin.Security.OpenIDConnect
和相关软件包,并且 Owin 启动是标准的,尽管我会指出这一行:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
我必须完全禁用表单身份验证;当 IIS 中启用匿名身份验证以外的任何其他功能时,我无法正常工作。
解决方案的核心实际上是我在这里找到的一个示例:如何在没有 aspnet 身份的情况下使用 OWIN 表单身份验证
/* URL validated, add authenticated claim */
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "PublicPC"),
new Claim(ClaimTypes.Email, "[email protected]")
};
var id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
var ctx = HttpContext.Current.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
但至关重要的是,我需要指定
CookieAuthenticationDefaults.AuthenticationType
,这就是我在 Owin 初创公司中使用的。
几年后提供了一个完整的、有效的解决方案,因为我在尝试在不同的应用程序中解决这个问题时再次发现了这个问题。
OpenIDConnect 和表单身份验证可以在同一个 .NET Framework 项目中共存,至少如果您同意表单身份验证主要负责限制对受保护资源的访问。
您将遇到的问题是,当表单身份验证开始限制对 OpenIDConnect 所需的路由的访问时。您需要做两件事才能使其正常工作:
更新您的 web.config 以允许匿名访问 OpenIDConnect URL,例如:
<location path="Account/SSOLogIn">
<system.web>
<authorization>
<allow users="?" />
</authorization>
</system.web>
</location>
<location path="signin-oidc">
<system.web>
<authorization>
<allow users="?" />
</authorization>
</system.web>
</location>
在 Global.asax.cs 中,告诉 Forms 身份验证中间件忽略所有与 OpenIDConnect 相关的路由:
// For small quantities, HashSet isn't really that much of an improvement over List,
// but using it makes the intention of the collection clear...
protected readonly HashSet<string> surpressedEndpoints =
[
"ssologin", "signin-oidc"
];
protected void Application_BeginRequest(object sender, EventArgs e)
{
// To support Forms authentication and OpenIDConnect, we have to prevent
// Forms authentication from interfering with OpenIDConnect URLs.
var lastSegmentLowercase = Context.Request.Url.Segments[Context.Request.Url.Segments.Length - 1].ToLower();
if (surpressedEndpoints.Contains(lastSegmentLowercase))
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
}
我们的应用程序现在使用具有多个身份提供商的 OpenIDConnect 和表单身份验证。我们处理 OpenIDConnect
SecurityTokenValidated
事件来识别经过身份验证的用户,然后在该事件中调用 FormsAuthentication.SetAuthCookie
。
我们的注销例程还会强力删除现有的身份验证 cookie:
// Remove any SSO cookies which may exist by sending new, expired, cookies.
Response.Cookies.Add(new HttpCookie(".AspNet.provider") { Expires = DateTime.Now.AddDays(-1) });