.NET 4.8 Owin OpenIdAuthentication:正式页面不返回302跳转问题
问题描述
为基于.NET Framework 4.8的老旧Web应用接入OpenID认证时,遇到以下异常情况:
- 测试页
Claims.aspx(继承自System.Web.UI.Page)调用HttpContext.Current.GetOwinContext().Authentication.Challenge()可正常触发302重定向到Keycloak完成授权流程。 - 将相同认证逻辑迁移到继承自
AuthPage的正式页面后,页面会继续加载,无法触发302跳转。 AuthPage已替换原Forms认证逻辑为OpenID的Challenge调用,但问题依旧。根据Challenge方法说明,该方法应向响应环境添加认证挑战信息,将状态码改为401或转为302重定向,但正式页面未触发该行为。
需求:在尽可能少改动现有应用代码的前提下解决问题。
相关代码
Startup.cs
public void ConfigureAuth(IAppBuilder app) { var notificationHandlers = new OpenIdConnectAuthenticationNotifications { AuthorizationCodeReceived = async (context) => { // Sign in the user here }, 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 { ClientId = _clientId, ClientSecret = _clientSecret, Authority = _authority, RedirectUri = _redirectUri, ResponseType = OpenIdConnectResponseType.CodeIdToken, Scope = OpenIdConnectScope.OpenIdProfile, TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", }, AuthenticationMode = AuthenticationMode.Active, Notifications = new OpenIdConnectAuthenticationNotifications { AuthorizationCodeReceived = async n => { // Exchange code for access and ID tokens var tokenClientOptions = new TokenClientOptions { ClientId = _clientId, ClientSecret = _clientSecret, Address = $"{_authority}/protocol/openid-connect/token" }; var tokenClient = new TokenClient(new HttpClient() { BaseAddress = new Uri($"{_authority}/protocol/openid-connect/token") }, tokenClientOptions); var client = new HttpClient(); var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest { Address = $"{_authority}/protocol/openid-connect/token", ClientId = _clientId, ClientSecret = _clientSecret, Code = n.Code, RedirectUri = _redirectUri, }); var client2 = new HttpClient(); var userInfoResponse = await client2.GetUserInfoAsync(new UserInfoRequest { Address = $"{_authority}protocol/openid-connect/userinfo", Token = tokenResponse.AccessToken }); var claims = new List<Claim>(userInfoResponse.Claims) { new Claim("id_token", tokenResponse.IdentityToken), new Claim("access_token", tokenResponse.AccessToken) }; n.AuthenticationTicket.Identity.AddClaims(claims); }, RedirectToIdentityProvider = notification => { if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout) { var logoutUri = $"https://{_redirectUri}/v2/logout?client_id={_clientId}"; var postLogoutUri = notification.ProtocolMessage.PostLogoutRedirectUri; if (!string.IsNullOrEmpty(postLogoutUri)) { if (postLogoutUri.StartsWith("/")) { // transform to absolute var request = notification.Request; postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri; } logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}"; } notification.Response.Redirect(logoutUri); notification.HandleResponse(); } notification.OwinContext.Response.Redirect("/Account/Logiasdfn"); notification.HandleResponse(); return Task.FromResult(0); } }, }); // Set Cookies as default authentication type app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = CookieAuthenticationDefaults.AuthenticationType, LoginPath = new PathString("/teset.aspx"), CookieSameSite = SameSiteMode.Lax, // More information on why the CookieManager needs to be set can be found here: // https://github.com/aspnet/AspNetKatana/wiki/System.Web-response-cookie-integration-issues CookieManager = new SameSiteCookieManager(new Microsoft.Owin.Host.SystemWeb.SystemWebCookieManager()) }); } }
Claims.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/MasterMainTest.master" AutoEventWireup="true" CodeBehind="Claims.aspx.cs" Inherits="RootNamespace.Web.Claims" %> <%@ Import Namespace="System.Security.Claims" %> <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder23" runat="server"> <h2>OpenID Connect Claims</h2> <dl> <asp:DataList runat="server" ID="dlClaims"> <ItemTemplate> <dt><%# ((Claim) Container.DataItem).Type %></dt> <dd><%# ((Claim) Container.DataItem).Value %></dd> </ItemTemplate> </asp:DataList> </dl> </asp:Content>
Claims.aspx.cs
public partial class Claims : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Request.IsAuthenticated) { HttpContext.Current.GetOwinContext().Authentication.Challenge(); } var claims = ClaimsPrincipal.Current.Claims; dlClaims.DataSource = claims; dlClaims.DataBind(); } }
AuthPage.cs
private void CheckSession(object sender, EventArgs ea) { /*OpenID authentication */ if (!Request.IsAuthenticated) { HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "~/test.aspx", AllowRefresh = true, IsPersistent = true }); } /* Forms authentication */ /* object o = Session["Admin"]; if ((o is Administrator) == false) { // OK, not logged in, save the address of this page // so we can return to it if/when the admin logs in Session["GoToWhenLoggedIn"] = Request.Url.PathAndQuery; Response.Redirect(Config.LoginPageName); } */ }
补充代码
public static void UseExternalSignInCookie(this IAppBuilder app) { UseExternalSignInCookie(app, DefaultAuthenticationTypes.ExternalCookie); } public static void UseExternalSignInCookie(this IAppBuilder app, string externalAuthenticationType) { if (app == null) { throw new ArgumentNullException("app"); } app.SetDefaultSignInAsAuthenticationType(externalAuthenticationType); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = externalAuthenticationType, AuthenticationMode = AuthenticationMode.Active, CookieName = CookiePrefix + externalAuthenticationType, ExpireTimeSpan = TimeSpan.FromMinutes(5), }); }
解决方案
1. 修复Startup.cs中RedirectToIdentityProvider的错误逻辑
当前OpenIdConnectAuthenticationOptions的RedirectToIdentityProvider处理存在两个致命问题:
- 非登出请求时,硬编码重定向到不存在的路径
/Account/Logiasdfn,并调用HandleResponse()终止正常OpenID流程。 - 定义的
notificationHandlers变量未被使用,属于冗余代码。
修改后的处理逻辑:
RedirectToIdentityProvider = notification => { if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout) { var logoutUri = $"{_authority}/protocol/openid-connect/logout?client_id={_clientId}"; var postLogoutUri = notification.ProtocolMessage.PostLogoutRedirectUri; if (!string.IsNullOrEmpty(postLogoutUri)) { if (postLogoutUri.StartsWith("/")) { var request = notification.Request; postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri; } logoutUri += $"&redirect_uri={ Uri.EscapeDataString(postLogoutUri)}"; } notification.Response.Redirect(logoutUri); notification.HandleResponse(); } // 非登出请求不拦截,让OpenID中间件自动处理重定向到Keycloak return Task.FromResult(0); }
同时删除未使用的notificationHandlers变量,减少冗余。
2. 确保AuthPage的认证检查时机正确
CheckSession方法需绑定到页面早期事件(如PreInit),若绑定到Load事件之后,页面可能已开始输出内容,导致重定向失效。调整AuthPage代码:
public class AuthPage : System.Web.UI.Page { protected override void OnInit(EventArgs e) { base.OnInit(e); PreInit += CheckSession; // 在页面初始化早期触发认证检查 } private void CheckSession(object sender, EventArgs ea) { if (!Request.IsAuthenticated) { // 使用当前请求路径作为跳转后返回地址,避免硬编码 var redirectUri = Request.Url.PathAndQuery; HttpContext.Current.GetOwinContext().Authentication.Challenge( new AuthenticationProperties { RedirectUri = redirectUri } ); Response.End(); // 终止页面后续处理,确保重定向生效 } } }
3. 修正CookieAuthenticationOptions的LoginPath拼写错误
当前LoginPath设置为/teset.aspx(拼写错误),修正为正确路径:
LoginPath = new PathString("/test.aspx"),
4. 调整Owin中间件加载顺序
中间件加载顺序必须遵循:Cookie认证 → 外部登录Cookie → OpenID认证。调整后的ConfigureAuth片段:
// Set Cookies as default authentication type app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); // 先加载Cookie认证中间件 app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = CookieAuthenticationDefaults.AuthenticationType, LoginPath = new PathString("/test.aspx"), CookieSameSite = SameSiteMode.Lax, CookieManager = new SameSiteCookieManager(new Microsoft.Owin.Host.SystemWeb.SystemWebCookieManager()) }); // 再加载外部登录Cookie app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // 最后加载OpenID认证中间件 app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { // 其他配置... });
5. 避免Response提前写入内容
若AuthPage或母版页在CheckSession前已向Response写入内容,重定向会失效。可在CheckSession中添加检查:
private void CheckSession(object sender, EventArgs ea) { if (!Request.IsAuthenticated) { if (!Response.IsRequestBeingRedirected && Response.ContentLength == 0) { var redirectUri = Request.Url.PathAndQuery; HttpContext.Current.GetOwinContext().Authentication.Challenge( new AuthenticationProperties { RedirectUri = redirectUri } ); Response.End(); } } }
内容的提问来源于stack exchange,提问作者Sputnikk23
相关产品推荐
相关产品推荐

