You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Framework4.8 Webforms应用iframe内Azure AD令牌刷新被拦截问题

问题描述

我们有一个运行在.NET Framework 4.8上的遗留WebForms应用,已更新为使用Azure/Entra身份验证,基于微软官方模板开发,常规场景运行正常,但存在以下问题:

  • 身份验证获取的ID令牌包含过期时间,Owin检测到令牌过期时会向login.microsoftonline.com请求刷新令牌,常规场景正常,但页面在iframe中时出现异常
  • 报错信息:

Refused to display 'https://login.microsoftonline.com/' in a frame because it set <code>X-Frame-Options</code> to <code>deny</code>

  • 异常时SecurityTokenValidated事件反复触发陷入循环,最终iframe显示损坏链接并抛出上述错误
  • 需求:在iframe场景下无需用户交互,静默刷新令牌并更新认证Cookie

参考Owin启动代码:

Public Sub ConfigureAuth(ByVal app As IAppBuilder)

    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)

    app.UseCookieAuthentication(New CookieAuthenticationOptions() With {.CookieSameSite = SameSiteMode.Lax, .CookieName = "appname.Auth"})

    app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions() With {
        .ClientId = clientId,
        .Authority = authority,
        .RedirectUri = redirectUri,
        .PostLogoutRedirectUri = postLogoutRedirectUri,
        .Notifications = New OpenIdConnectAuthenticationNotifications() With {
            .AuthenticationFailed = Function(context) System.Threading.Tasks.Task.FromResult(0),
            .SecurityTokenValidated = Function(context)
                                          Return OnSecurityTokenValidated(context)
                                      End Function
        }}
    )

    app.UseStageMarker(PipelineStage.Authenticate)

End Sub
解决方案

1. 配置OpenID Connect以获取刷新令牌

默认情况下,OpenID Connect中间件不会请求刷新令牌,需要显式设置Scope包含offline_access,同时切换为授权码流以获取刷新令牌:

app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions() With {
    .ClientId = clientId,
    .Authority = authority,
    .RedirectUri = redirectUri,
    .PostLogoutRedirectUri = postLogoutRedirectUri,
    .Scope = OpenIdConnectScope.OpenIdProfile + " offline_access", ' 添加离线访问权限以获取刷新令牌
    .ResponseType = OpenIdConnectResponseType.CodeIdToken, ' 使用授权码流
    .Notifications = New OpenIdConnectAuthenticationNotifications() With {
        .AuthenticationFailed = Function(context) 
                                    ' 捕获iframe相关错误,避免循环重定向
                                    If context.Exception.Message.Contains("X-Frame-Options") Then
                                        context.HandleResponse()
                                        context.Response.Redirect("/Error/IframeAuthExpired") ' 自定义静默处理逻辑或错误页
                                    End If
                                    Return System.Threading.Tasks.Task.FromResult(0)
                                End Function,
        .AuthorizationCodeReceived = Async Function(context)
                                        ' 用授权码交换令牌,并存储刷新令牌到身份票据
                                        Dim tokenClient = New TokenClient(
                                            $"{authority}/oauth2/token",
                                            clientId,
                                            clientSecret ' 需要在Entra应用注册中配置客户端密钥
                                        )
                                        Dim tokenResponse = Await tokenClient.RequestAuthorizationCodeAsync(
                                            context.Code,
                                            redirectUri
                                        )
                                        ' 将刷新令牌存入ClaimsIdentity
                                        context.AuthenticationTicket.Identity.AddClaim(
                                            New Claim("refresh_token", tokenResponse.RefreshToken)
                                        )
                                        Return System.Threading.Tasks.Task.FromResult(0)
                                    End Function,
        .SecurityTokenValidated = Function(context)
                                      Return OnSecurityTokenValidated(context)
                                  End Function
    }}
)

2. 自定义Cookie认证的静默刷新逻辑

修改CookieAuthenticationOptions,添加OnValidateIdentity事件,在令牌即将过期时使用刷新令牌静默获取新令牌,避免触发重定向:

app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
    .CookieSameSite = SameSiteMode.None, ' iframe场景下需设置为None,同时确保HTTPS环境
    .CookieName = "appname.Auth",
    .CookieSecure = CookieSecureOption.Always, ' 生产环境强制HTTPS
    .SlidingExpiration = True,
    .ExpireTimeSpan = TimeSpan.FromMinutes(30),
    .Provider = New CookieAuthenticationProvider() With {
        .OnValidateIdentity = Async Function(context)
                                  ' 检查ID令牌是否即将过期
                                  Dim expClaim = context.Identity.FindFirst(ClaimTypes.Expiration)
                                  If expClaim IsNot Nothing Then
                                      Dim expirationDate = DateTimeOffset.Parse(expClaim.Value)
                                      ' 提前5分钟触发刷新
                                      If expirationDate <= DateTimeOffset.UtcNow.AddMinutes(5) Then
                                          ' 从Claims中取出刷新令牌
                                          Dim refreshToken = context.Identity.FindFirst("refresh_token")?.Value
                                          If Not String.IsNullOrEmpty(refreshToken) Then
                                              Try
                                                  ' 使用刷新令牌获取新的ID令牌
                                                  Dim tokenClient = New TokenClient(
                                                      $"{authority}/oauth2/token",
                                                      clientId,
                                                      clientSecret
                                                  )
                                                  Dim tokenResponse = Await tokenClient.RequestRefreshTokenAsync(refreshToken)
                                                  ' 更新身份票据中的令牌信息
                                                  Dim newIdentity = New ClaimsIdentity(context.Identity)
                                                  ' 替换过期时间和ID令牌
                                                  newIdentity.RemoveClaim(newIdentity.FindFirst(ClaimTypes.Expiration))
                                                  newIdentity.AddClaim(New Claim(ClaimTypes.Expiration, DateTimeOffset.UtcNow.AddSeconds(tokenResponse.ExpiresIn).ToString()))
                                                  newIdentity.RemoveClaim(newIdentity.FindFirst("id_token"))
                                                  newIdentity.AddClaim(New Claim("id_token", tokenResponse.IdToken))
                                                  ' 更新刷新令牌(若返回新的刷新令牌)
                                                  If Not String.IsNullOrEmpty(tokenResponse.RefreshToken) Then
                                                      newIdentity.RemoveClaim(newIdentity.FindFirst("refresh_token"))
                                                      newIdentity.AddClaim(New Claim("refresh_token", tokenResponse.RefreshToken))
                                                  End If
                                                  ' 替换当前身份并标记需要续签Cookie
                                                  context.ReplaceIdentity(newIdentity)
                                                  context.ShouldRenew = True
                                              Catch ex As Exception
                                                  ' 刷新失败时标记身份无效,后续可触发静默提示
                                                  context.RejectIdentity()
                                              End Try
                                          End If
                                      End If
                                  End If
                                  Return System.Threading.Tasks.Task.FromResult(0)
                              End Function
    }
})

3. 补充iframe场景适配

  • 前端页面加载前,通过AJAX调用后端专门的刷新接口,提前检测并更新令牌,避免触发Owin的自动重定向逻辑
  • 确保后端接口返回的Cookie设置SameSite=None和Secure属性,适配iframe跨域场景
  • 在AuthenticationFailed事件中捕获iframe相关错误时,优先返回静默处理逻辑,而非重定向到登录页

4. Azure Entra配置要点

  • 应用注册中需开启授权码流(在「认证」->「高级设置」中启用)
  • 客户端密钥需妥善存储,避免硬编码(可使用配置文件或Azure Key Vault)
  • 确保RedirectUri已添加到应用注册的允许回调地址列表中

内容的提问来源于stack exchange,提问作者n4esa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:25:08