Vb.net 使用OWIN/MSAL的WebForms应用程序未收到授权代码
我的任务是通过将几个WebForms应用程序迁移到MSAL v4来修改它们。我从GitHub下载了一个可以工作的MVC示例(),它运行得非常完美。我已经成功地模拟了MVC示例,直至初始令牌缓存。OWIN单租户登录过程按预期执行;但是,分配给处理通知接收(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
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
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方法与语言无关。我不明白为什么这两个应用程序会对它们有任何不同的对待。再次感谢。