Vb.net 使用OWIN/MSAL的WebForms应用程序未收到授权代码

Vb.net 使用OWIN/MSAL的WebForms应用程序未收到授权代码,vb.net,webforms,microsoft-graph-api,msal,Vb.net,Webforms,Microsoft Graph Api,Msal,我的任务是通过将几个WebForms应用程序迁移到MSAL v4来修改它们。我从GitHub下载了一个可以工作的MVC示例(),它运行得非常完美。我已经成功地模拟了MVC示例,直至初始令牌缓存。OWIN单租户登录过程按预期执行;但是,分配给处理通知接收(OnAuthorizationCodeReceivedAsync)的任务从未被激发。因此,会话缓存中没有放置令牌 OWIN中间件在启动时实例化如下: Public Sub ConfigureAuth(ByVal app As IAppBuilde

我的任务是通过将几个WebForms应用程序迁移到MSAL v4来修改它们。我从GitHub下载了一个可以工作的MVC示例(),它运行得非常完美。我已经成功地模拟了MVC示例,直至初始令牌缓存。OWIN单租户登录过程按预期执行;但是,分配给处理通知接收(
OnAuthorizationCodeReceivedAsync
)的
任务
从未被激发。因此,会话缓存中没有放置令牌

OWIN中间件在启动时实例化如下:

Public Sub ConfigureAuth(ByVal app As IAppBuilder)
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - STARTED" & vbLf)

    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
    app.UseCookieAuthentication(New CookieAuthenticationOptions())
    app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
        .ClientId = appId,
        .Scope = $"openid email profile offline_access {graphScopes}",
        .Authority = sAuthority,
        .RedirectUri = redirectUri,
        .PostLogoutRedirectUri = redirectUri,
        .TokenValidationParameters = New TokenValidationParameters With {
            .ValidateIssuer = False
        },
        .Notifications = New OpenIdConnectAuthenticationNotifications With {
            .AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
            .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync
        }
    })
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - COMPLETED" & vbLf)
End Sub
Private Shared Function OnAuthenticationFailedAsync(ByVal notification As AuthenticationFailedNotification(Of OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthenticationFailedAsync()" & vbLf)

    notification.HandleResponse()
    Dim redirect As String = $"~/Views/ErrorPage?message={notification.Exception.Message}"

    If notification.ProtocolMessage IsNot Nothing AndAlso Not String.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription) Then
        redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}"
    End If

    notification.Response.Redirect(redirect)
    Return Task.FromResult(0)
End Function


Private Async Function OnAuthorizationCodeReceivedAsync(ByVal notification As AuthorizationCodeReceivedNotification) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthorizationCodeReceivedAsync()" & vbLf)

    Dim signedInUser = New ClaimsPrincipal(notification.AuthenticationTicket.Identity)
    Dim idClient As IConfidentialClientApplication = ConfidentialClientApplicationBuilder.Create(appId).WithRedirectUri(redirectUri).WithClientSecret(appSecret).Build()
    Dim tokenStore As SessionTokenStore = New SessionTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser)

    Try
        Dim scopes As String() = graphScopes.Split(" "c)
        Dim authResult = Await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync()
        Dim userDetails = Await Helpers.GraphHelper.GetUserDetailsAsync(authResult.AccessToken)
        Dim cachedUser = New CachedUser() With {
            .DisplayName = userDetails.DisplayName,
            .Email = If(String.IsNullOrEmpty(userDetails.Mail), userDetails.UserPrincipalName, userDetails.Mail),
            .Avatar = String.Empty,
            .CompanyName = userDetails.CompanyName
        }
        tokenStore.SaveUserDetails(cachedUser)
    Catch ex As MsalException
        Dim message As String = "AcquireTokenByAuthorizationCodeAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    Catch ex As Microsoft.Graph.ServiceException
        Dim message As String = "GetUserDetailsAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    End Try

End Function
Public Shared Sub SignIn()
    System.Diagnostics.Debug.WriteLine("AccountController.vb SignIn()")

    If Not HttpContext.Current.Request.IsAuthenticated Then
        HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(New AuthenticationProperties With {
            .RedirectUri = "/"
        }, OpenIdConnectAuthenticationDefaults.AuthenticationType)
    End If
End Sub
请注意,OWIN配置了一对通知,一个用于指示成功获取授权代码(AuthorizationCodeReceived),另一个用于指示身份验证失败(AuthenticationFailed)。每个都映射到相应的异步任务对象。任务定义如下:

Public Sub ConfigureAuth(ByVal app As IAppBuilder)
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - STARTED" & vbLf)

    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
    app.UseCookieAuthentication(New CookieAuthenticationOptions())
    app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
        .ClientId = appId,
        .Scope = $"openid email profile offline_access {graphScopes}",
        .Authority = sAuthority,
        .RedirectUri = redirectUri,
        .PostLogoutRedirectUri = redirectUri,
        .TokenValidationParameters = New TokenValidationParameters With {
            .ValidateIssuer = False
        },
        .Notifications = New OpenIdConnectAuthenticationNotifications With {
            .AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
            .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync
        }
    })
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - COMPLETED" & vbLf)
End Sub
Private Shared Function OnAuthenticationFailedAsync(ByVal notification As AuthenticationFailedNotification(Of OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthenticationFailedAsync()" & vbLf)

    notification.HandleResponse()
    Dim redirect As String = $"~/Views/ErrorPage?message={notification.Exception.Message}"

    If notification.ProtocolMessage IsNot Nothing AndAlso Not String.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription) Then
        redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}"
    End If

    notification.Response.Redirect(redirect)
    Return Task.FromResult(0)
End Function


Private Async Function OnAuthorizationCodeReceivedAsync(ByVal notification As AuthorizationCodeReceivedNotification) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthorizationCodeReceivedAsync()" & vbLf)

    Dim signedInUser = New ClaimsPrincipal(notification.AuthenticationTicket.Identity)
    Dim idClient As IConfidentialClientApplication = ConfidentialClientApplicationBuilder.Create(appId).WithRedirectUri(redirectUri).WithClientSecret(appSecret).Build()
    Dim tokenStore As SessionTokenStore = New SessionTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser)

    Try
        Dim scopes As String() = graphScopes.Split(" "c)
        Dim authResult = Await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync()
        Dim userDetails = Await Helpers.GraphHelper.GetUserDetailsAsync(authResult.AccessToken)
        Dim cachedUser = New CachedUser() With {
            .DisplayName = userDetails.DisplayName,
            .Email = If(String.IsNullOrEmpty(userDetails.Mail), userDetails.UserPrincipalName, userDetails.Mail),
            .Avatar = String.Empty,
            .CompanyName = userDetails.CompanyName
        }
        tokenStore.SaveUserDetails(cachedUser)
    Catch ex As MsalException
        Dim message As String = "AcquireTokenByAuthorizationCodeAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    Catch ex As Microsoft.Graph.ServiceException
        Dim message As String = "GetUserDetailsAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    End Try

End Function
Public Shared Sub SignIn()
    System.Diagnostics.Debug.WriteLine("AccountController.vb SignIn()")

    If Not HttpContext.Current.Request.IsAuthenticated Then
        HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(New AuthenticationProperties With {
            .RedirectUri = "/"
        }, OpenIdConnectAuthenticationDefaults.AuthenticationType)
    End If
End Sub
用户登录按如下方式启动:

Public Sub ConfigureAuth(ByVal app As IAppBuilder)
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - STARTED" & vbLf)

    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
    app.UseCookieAuthentication(New CookieAuthenticationOptions())
    app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
        .ClientId = appId,
        .Scope = $"openid email profile offline_access {graphScopes}",
        .Authority = sAuthority,
        .RedirectUri = redirectUri,
        .PostLogoutRedirectUri = redirectUri,
        .TokenValidationParameters = New TokenValidationParameters With {
            .ValidateIssuer = False
        },
        .Notifications = New OpenIdConnectAuthenticationNotifications With {
            .AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
            .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync
        }
    })
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb ConfigureAuth() - COMPLETED" & vbLf)
End Sub
Private Shared Function OnAuthenticationFailedAsync(ByVal notification As AuthenticationFailedNotification(Of OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthenticationFailedAsync()" & vbLf)

    notification.HandleResponse()
    Dim redirect As String = $"~/Views/ErrorPage?message={notification.Exception.Message}"

    If notification.ProtocolMessage IsNot Nothing AndAlso Not String.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription) Then
        redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}"
    End If

    notification.Response.Redirect(redirect)
    Return Task.FromResult(0)
End Function


Private Async Function OnAuthorizationCodeReceivedAsync(ByVal notification As AuthorizationCodeReceivedNotification) As Task
    System.Diagnostics.Debug.WriteLine(vbLf & "Startup.Auth.vb OnAuthorizationCodeReceivedAsync()" & vbLf)

    Dim signedInUser = New ClaimsPrincipal(notification.AuthenticationTicket.Identity)
    Dim idClient As IConfidentialClientApplication = ConfidentialClientApplicationBuilder.Create(appId).WithRedirectUri(redirectUri).WithClientSecret(appSecret).Build()
    Dim tokenStore As SessionTokenStore = New SessionTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser)

    Try
        Dim scopes As String() = graphScopes.Split(" "c)
        Dim authResult = Await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync()
        Dim userDetails = Await Helpers.GraphHelper.GetUserDetailsAsync(authResult.AccessToken)
        Dim cachedUser = New CachedUser() With {
            .DisplayName = userDetails.DisplayName,
            .Email = If(String.IsNullOrEmpty(userDetails.Mail), userDetails.UserPrincipalName, userDetails.Mail),
            .Avatar = String.Empty,
            .CompanyName = userDetails.CompanyName
        }
        tokenStore.SaveUserDetails(cachedUser)
    Catch ex As MsalException
        Dim message As String = "AcquireTokenByAuthorizationCodeAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    Catch ex As Microsoft.Graph.ServiceException
        Dim message As String = "GetUserDetailsAsync threw an exception"
        notification.HandleResponse()
        notification.Response.Redirect($"~/Views/ErrorPage?message={message}&debug={ex.Message}")
    End Try

End Function
Public Shared Sub SignIn()
    System.Diagnostics.Debug.WriteLine("AccountController.vb SignIn()")

    If Not HttpContext.Current.Request.IsAuthenticated Then
        HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(New AuthenticationProperties With {
            .RedirectUri = "/"
        }, OpenIdConnectAuthenticationDefaults.AuthenticationType)
    End If
End Sub
我没有收到任何运行时错误消息。没有生成错误或警告。一旦OWIN处理好登录过程,应用程序就会挂起

简而言之,我试图理解为什么程序流没有从
GetOwinContext().Authentication.Challenge()
方法传递到
OnAuthorizationCodeReceivedAsync()
任务。我已经从正在运行的MVC示例中验证了这是预期的行为

编辑:

在跟踪应用程序的MVC/C#和WebForms/VB.NET版本后,并列比较这两个版本表明应用程序的WebForms版本挂起在UseOpenIdConnectAuthentication()方法上。关联的OpenIdConnectAuthenticationNotifications已扩展为包括所有六个可用选项

从MVC/C#Startup.Auth.cs:

            app.UseOpenIdConnectAuthentication(
              new OpenIdConnectAuthenticationOptions
              {
                  ClientId = appId,
                  Scope = $"openid email profile offline_access {graphScopes}",
                  Authority = "https://login.microsoftonline.com/common/v2.0",
                  RedirectUri = redirectUri,
                  PostLogoutRedirectUri = redirectUri,
                  TokenValidationParameters = new TokenValidationParameters
                  {
                      ValidateIssuer = false
                  },
                  Notifications = new OpenIdConnectAuthenticationNotifications
                  {
                      AuthenticationFailed = OnAuthenticationFailedAsync,
                      AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync,
                      RedirectToIdentityProvider = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** RedirectToIdentityProvider");
                          return Task.FromResult(0);
                      },
                      MessageReceived = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** MessageReceived");
                          return Task.FromResult(0);
                      },
                      SecurityTokenReceived = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** SecurityTokenReceived");
                          return Task.FromResult(0);
                      },
                      SecurityTokenValidated = (context) =>
                      {
                          System.Diagnostics.Debug.WriteLine("*** SecurityTokenValidated");
                          return Task.FromResult(0);
                      }
                  }
              }
            );
收到下列通知:

  • 重定向到身份验证提供程序
  • 收到的消息
  • 已收到SecurityToken
  • SecurityTokenValidated
--启动OnAuthorizationCodeReceivedAsync()方法,并按预期检索和缓存访问令牌

从WebForms/VB.NET Startup.Auth.VB:

        app.UseOpenIdConnectAuthentication(New OpenIdConnectAuthenticationOptions With {
            .ClientId = appId,
            .Scope = $"openid email profile offline_access {graphScopes}",
            .Authority = sAuthority,
            .RedirectUri = redirectUri,
            .PostLogoutRedirectUri = redirectUri,
            .TokenValidationParameters = New TokenValidationParameters With {
                .ValidateIssuer = False
            },
            .Notifications = New OpenIdConnectAuthenticationNotifications With {
                .AuthenticationFailed = AddressOf OnAuthenticationFailedAsync,
                .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceivedAsync,
                .RedirectToIdentityProvider = Function(context)
                                                  Debug.WriteLine("*** RedirectToIdentityProvider")
                                                  Return Task.FromResult(0)
                                              End Function,
                .MessageReceived = Function(context)
                                       Debug.WriteLine("*** MessageReceived")
                                       Return Task.FromResult(0)
                                   End Function,
                .SecurityTokenReceived = Function(context)
                                             Debug.WriteLine("*** SecurityTokenReceived")
                                             Return Task.FromResult(0)
                                         End Function,
                .SecurityTokenValidated = Function(context)
                                              Debug.WriteLine("*** SecurityTokenValidated")
                                              Return Task.FromResult(0)
                                          End Function
            }
        })

收到以下通知: -重定向到身份验证提供程序

--应用程序在等待时挂起,不会触发其他事件

我试图理解为什么相同的OpenID连接方法会导致此应用程序的MVC和WebForms版本之间的行为有如此显著的不同。您没有,因此我不确定登录是否有效。(除非MSAL将响应类型默认为id_令牌,这是可能的。)

您应该能够使用

无论如何,OpenIdConnect通常不使用授权代码流进行用户登录。因此,AuthorizationCodeReceived事件不会发生


现在,如果您的应用程序希望在登录后访问受保护的资源(Microsoft Graph、SharePoint、AAD secured WebAPI),则AuthCode流是合适的。这将是最重要的。

谢谢您的回复。遗憾的是,插入响应类型没有帮助。我被“OpenIdConnect不…用户登录流”弄糊涂了。如果AuthCode流适用于MVC示例的登录,从逻辑上讲,它应该适用于WebForms。我们反复检查了转换后的VB代码与C#代码的对比,结果看起来不错。登录过程似乎也可以正常工作。Azure AD端点响应“login.microsoftonline.com/kmsi”URL,但这就是我们挂断的地方。我们的客户任务没有响应。非常感谢您的帮助。再次感谢!由于未使用授权代码流,因此不会触发事件。您在该事件中拥有的代码属于用户登录后运行的页面的代码隐藏。通常,重定向uri是帐户控制器/页面。Paul,我发布了一个编辑,显示:(a)UseOpenIdConnectAuthentication方法的C#和VB.NET版本;和(b)接收到的OpenID连接通知的差异。据我所知,UseOpenIdConnectAuthentication方法与语言无关。我不明白为什么这两个应用程序会对它们有任何不同的对待。再次感谢。