【问题标题】:ASP.NET OWIN OpenID Connect not creating user authenticationASP.NET OWIN OpenID Connect 未创建用户身份验证
【发布时间】:2019-09-11 19:17:09
【问题描述】:

我有一个 ASP.NET 4.6 Web 应用程序,我正在尝试使用 OWIN 添加 OpenId Connect。

我添加了我的 Owin 启动类,一切似乎都配置正确,但我遇到的问题是 ASP 身份/身份验证用户永远不会被创建。我最终陷入了一个无限循环,其中 OpenId 回调页面重定向回原始页面,然后重定向到登录页面等。

这是我的启动课:

public void Configuration(IAppBuilder app)
    {


     app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);


        app.UseKentorOwinCookieSaver();
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Login.aspx"),
            ExpireTimeSpan = TimeSpan.FromDays(7)
        });

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {                
            ClientId = _clientId,
            ClientSecret = _clientSecret,
            Authority = _authority,
            RedirectUri = _redirectUri, // LoginCallback
            PostLogoutRedirectUri = "http://localhost:60624/Logout.aspx",

            ResponseType = OpenIdConnectResponseType.CodeIdToken,
            Scope = "openid profile email",

            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name"
            },

            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = async n =>
                {
                    // Exchange code for access and ID tokens
                    var tokenClient = new TokenClient($"{_authority}/as/token.oauth2", _clientId, _clientSecret);

                    var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, _redirectUri);
                    if (tokenResponse.IsError)
                    {
                        throw new Exception(tokenResponse.Error);
                    }

                    var userInfoClient = new UserInfoClient($"{_authority}/idp/userinfo.openid");
                    var userInfoResponse = await userInfoClient.GetAsync(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);



                    //// create the identity
                    //var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);

                    //System.Web.HttpContext.Current.GetOwinContext().Authentication.SignIn(new AuthenticationProperties
                    //{
                    //    IsPersistent = true
                    //}, identity);
                }
            }
        });
    }

这里是 Login.aspx 页面:

 protected void Page_Load(object sender, EventArgs e)
    {

        if (!Request.IsAuthenticated)
        {
            HttpContext.Current.GetOwinContext().Authentication.Challenge(
              new AuthenticationProperties { RedirectUri = Request["ReturnUrl"] ?? "Default.aspx" },
              OpenIdConnectAuthenticationDefaults.AuthenticationType);
        }        
    }

页面流程是这样的:

1) 请求:http://localhost:60624/Page.aspx 响应:302 - 重定向到 Login.aspx

2) 请求:http://localhost:60624/Login.aspx?ReturnUrl=%2FPage.aspx 响应 302 - 重定向到 https://auth.myprovider.com

在响应标头上设置了一些 cookie:

设置 Cookie:OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=xxxxxxxxx;路径=/;过期=格林威治标准时间 2019 年 4 月 22 日星期一 14:12:00; HttpOnly 设置 Cookie:OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=yyyyyyyyy;过期=格林威治标准时间 2019 年 4 月 22 日星期一 14:12:00;路径=/; HttpOnly

3) Auth 提供者,登录,它 302 重定向到 /LoginCallback

4) 请求:http://localhost:60624/LoginCallback 响应 302 - 重定向到 /Page.aspx

步骤 2 中设置的 Cookie 将在此处清除。

设置 Cookie:OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=;路径=/; expires=周四,1970 年 1 月 1 日 00:00:00 GMT 设置 Cookie:OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=;过期=周四,1970 年 1 月 1 日 00:00:00 GMT;路径=/

5) 返回Page.aspx,用户未通过身份验证;转到第 1 步

我已经进行了一些调试,并且 AuthorizationCodeReceived 在 Startup 上触发,并且后端成功调用了 User Info 端点。我尝试从该通知中调用 System.Web.HttpContext.Current.GetOwinContext().Authentication.SignIn(),但这似乎没有任何作用。

在这一点上,我被困住了。为什么未设置用户身份的身份验证 cookie?似乎这应该自动发生。我应该自己手动创建吗? (How can I manually create a authentication cookie instead of the default method?)

编辑:在查看 @Zaxxon 的回复后,我能够让它工作。 AuthorizationCodeReceived 通知中有两处错误

  1. 我需要创建 ClaimsIdentity。在我上面提交的原始代码中,我已经注释掉了这一点,但它也是不正确的。
  2. 我不得不用刚刚创建的新身份替换 AuthenticationTicket。然后将声明添加到这个新身份。

这是工作代码:

ClaimsIdentity identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.GivenName, ClaimTypes.Role);
 n.AuthenticationTicket = new AuthenticationTicket(identity, n.AuthenticationTicket.Properties);
 n.AuthenticationTicket.Identity.AddClaims(claims);

【问题讨论】:

  • 服务器上已有的cookie可能无效或过期。我会使用 IE 并删除所有 cookie 并重试。我认为您遇到了异常,应该在发生异常时退出代码而不是重试。错误 302 看起来像是某种端口转发算法。请参阅:en.wikipedia.org/wiki/List_of_HTTP_status_codes
  • 没有错误。 HTTP 302 是此过程中的正常重定向响应,因为它在页面流之间移动。在此过程中,我没有看到任何异常,即使在清除 cookie 之后也是如此。
  • 你看到 200 OK 了吗?我怀疑您遇到异常并会添加异常处理程序。还会检查事件查看器以查看是否有任何错误。
  • 我没有看到 200 OK,因为用户从未经过身份验证。正如我提到的,在通过身份验证流程之后,它会重复自己,因为在身份验证期间从未设置过 cookie。我应该在 OWIN 进程的哪里添加异常处理程序?
  • 您需要使用像 wireshark 或 fiddler 这样的嗅探器,并首先确认您正在发送请求。一旦请求被发送到服务器,服务器应该发回一个状态为 200 OK(或失败状态)的响应。现在根据您的描述,我不确定是否曾经发送过请求。我也无法判断这是安全的(使用 SSL 或 TTLS)还是不安全的。然后我不知道您使用的是 http 1.0(流模式)还是 http 1.1(块模式)。服务器可能没有运行,因此从 cmd.exe 使用 > Netstat -a 并验证端口 60624 上是否有侦听器。

标签: c# owin openid-connect owin-middleware


【解决方案1】:

是的,我不得不在 VB.Net 中获得一个概念证明来解决这个问题,这有点痛苦。这是我的测试代码(即不是生产代码),它基于我看到的其他一些 C# 互联网示例:

Imports System.Security.Claims
Imports System.Threading.Tasks
Imports IdentityModel
Imports IdentityModel.Client
Imports Microsoft.AspNet.Identity
Imports Microsoft.AspNet.Identity.Owin
Imports Microsoft.IdentityModel.Protocols.OpenIdConnect
Imports Microsoft.Owin
Imports Microsoft.Owin.Security
Imports Microsoft.Owin.Security.Cookies
Imports Microsoft.Owin.Security.Notifications
Imports Microsoft.Owin.Security.OAuth
Imports Microsoft.Owin.Security.OpenIdConnect
Imports Owin

Partial Public Class Startup
    Private Shared _oAuthOptions As OAuthAuthorizationServerOptions
    Private Shared _publicClientId As String

    Private Shared _clientId As String
    Private Shared _clientSecret As String

    ' Enable the application to use OAuthAuthorization. You can then secure your Web APIs
    Shared Sub New()

        _clientId = System.Configuration.ConfigurationManager.AppSettings("OAuth:ClientID").ToString()
        _clientSecret = System.Configuration.ConfigurationManager.AppSettings("OAuth:SecretKey").ToString()

        PublicClientId = _clientId

        OAuthOptions = New OAuthAuthorizationServerOptions() With {
            .TokenEndpointPath = New PathString("/Token"), 'New PathString("https://authtesteria.domain.com/as/token.oauth2"), ' 
            .AuthorizeEndpointPath = New PathString("/Account/Authorize"), 'New PathString("https://authtesteria.domain.com/as/authorization.oauth2"), '
            .Provider = New ApplicationOAuthProvider(PublicClientId),
            .AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            .AllowInsecureHttp = True
        }
    End Sub

    Public Shared Property OAuthOptions() As OAuthAuthorizationServerOptions
        Get
            Return _oAuthOptions
        End Get
        Private Set
            _oAuthOptions = Value
        End Set
    End Property

    Public Shared Property PublicClientId() As String
        Get
            Return _publicClientId
        End Get
        Private Set
            _publicClientId = Value
        End Set
    End Property

    ' For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
    Public Sub ConfigureAuth(app As IAppBuilder)
        ' Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)
        app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)
        app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)

        ' Enable the application to use a cookie to store information for the signed in user
        ' and to use a cookie to temporarily store inforation about a user logging in with a third party login provider
        ' Configure the sign in cookie
        ' OnValidateIdentity enables the application to validate the security stamp when the user logs in.
        ' This is a security feature which is used when you change a password or add an external login to your account.
        app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
            .AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            .Provider = New CookieAuthenticationProvider() With {
                .OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser)(
                    validateInterval:=TimeSpan.FromMinutes(30),
                    regenerateIdentity:=Function(manager, user) user.GenerateUserIdentityAsync(manager))},
            .LoginPath = New PathString("/Account/Login")})


        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)

        ' Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5))

        ' Enables the application to remember the second login verification factor such as phone or email.
        ' Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
        ' This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie)

        ' Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions)

        Dim controller As New AccountController()

        'Dim validator As OpenIdConnectProtocolValidator = New OpenIdConnectProtocolValidator()
        'validator.ShowPII = False

        Dim oidcAuth As New Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions() With {
            .ClientId = _clientId,
            .ClientSecret = _clientSecret,
            .Authority = "https://authtesteria.domain.com",
            .Notifications = New Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationNotifications() With {
                .RedirectToIdentityProvider = AddressOf OnRedirectToIdentityProvider,
                .MessageReceived = AddressOf OnMessageReceived,
                .SecurityTokenReceived = AddressOf OnSecurityTokenReceived,
                .SecurityTokenValidated = AddressOf OnSecurityTokenValidated,
                .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceived,
                .AuthenticationFailed = AddressOf OnAuthenticationFailed
        }}
        app.UseOpenIdConnectAuthentication(oidcAuth)

    End Sub

    Private Function OnRedirectToIdentityProvider(arg As RedirectToIdentityProviderNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
        Debug.WriteLine("*** RedirectToIdentityProvider")

        If arg.ProtocolMessage.RequestType = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Logout Then
            Dim idTokenHint = arg.OwinContext.Authentication.User.FindFirst("id_token")

            If idTokenHint IsNot Nothing Then
                arg.ProtocolMessage.IdTokenHint = idTokenHint.Value
            End If
        End If
        Return Task.FromResult(0)
    End Function

    Private Function OnMessageReceived(arg As MessageReceivedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
        Debug.WriteLine("*** MessageReceived")
        Return Task.FromResult(0)
    End Function

    Private Function OnAuthorizationCodeReceived(arg As AuthorizationCodeReceivedNotification) As Task
        Debug.WriteLine("*** AuthorizationCodeReceived")
        'Upon successful sign in, get & cache a token if you want here
        Return Task.FromResult(0)
    End Function

    Private Function OnAuthenticationFailed(arg As AuthenticationFailedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
        Debug.WriteLine("*** AuthenticationFailed")
        Return Task.FromResult(0)
    End Function

    Private Function OnSecurityTokenReceived(arg As SecurityTokenReceivedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
        Debug.WriteLine("*** SecurityTokenReceived")
        Return Task.FromResult(0)
    End Function

    Private Async Function OnSecurityTokenValidated(arg As SecurityTokenValidatedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task
        Debug.WriteLine("*** SecurityTokenValidated")
        'Verify the user signing in should have access or not.  Here I just pass folk thru.
        Dim nid = New ClaimsIdentity(
              DefaultAuthenticationTypes.ApplicationCookie, 'arg.AuthenticationTicket.Identity.AuthenticationType,
              ClaimTypes.GivenName,
              ClaimTypes.Role)

        Dim tokenClient = New TokenClient("https://authtesteria.domain.com/as/token.oauth2",
             _clientId,
             _clientSecret)

        Dim tokenResponse = Await tokenClient.RequestAuthorizationCodeAsync(arg.ProtocolMessage.Code, arg.ProtocolMessage.RedirectUri)

        ' get userinfo data
        Dim userInfoClient = New IdentityModel.Client.UserInfoClient("https://authtesteria.domain.com/idp/userinfo.openid")

        Dim userInfo = Await userInfoClient.GetAsync(tokenResponse.AccessToken)
        userInfo.Claims.ToList().ForEach(Sub(ui) nid.AddClaim(New Claim(ui.Type, ui.Value)))

        '' keep the id_token for logout
        'nid.AddClaim(New Claim("id_token", arg.ProtocolMessage.IdToken))

        '' add access token for sample API
        'nid.AddClaim(New Claim("access_token", arg.ProtocolMessage.AccessToken))

        '' keep track of access token expiration
        'nid.AddClaim(New Claim("expires_at", DateTimeOffset.Now.AddSeconds(Integer.Parse(arg.ProtocolMessage.ExpiresIn)).ToString()))

        '' add some other app specific claim
        'nid.AddClaim(New Claim("app_specific", "some data"))

        nid.AddClaim(New Claim(ClaimTypes.Role, "group1"))

        arg.AuthenticationTicket = New AuthenticationTicket(nid, arg.AuthenticationTicket.Properties)
        arg.AuthenticationTicket.Properties.RedirectUri = HttpContext.Current.Session("PageRedirect").ToString() 
    End Function
End Class

现在我像这样触发登录:

Private Sub SomePageName_Load(sender As Object, e As EventArgs) Handles Me.Load
    If Not IsPostBack Then
        If User.Identity.IsAuthenticated Then
            Console.WriteLine(User.Identity.GetUserName())
        Else
            Session("PageRedirect") = Request.Url
            Response.Redirect("/")
        End If
    End If
End Sub

我们有一些不同:

  1. 我使用 OnSecurityTokenValidated 但我不确定这是否重要
  2. 我用当前页面的 Request.Url 填充一个 Session 变量,
  3. 然后在启动时在 OnSecurityTokenValidated 的通知参数中使用它:arg.AuthenticationTicket.Properties.RedirectUri = ...(参见我的代码)。

希望这会有所帮助。享受吧!

【讨论】:

  • 我打算提交一些编辑以修复您的答案上的语法突出显示(强制 VB 突出显示),但 Stack Overflow 声称建议的编辑队列已满。 (我想知道这个队列是专门针对这个答案、问题还是整个 Stack Overflow...
猜你喜欢
  • 2017-07-10
  • 1970-01-01
  • 2015-05-12
  • 2017-12-14
  • 2021-09-07
  • 2016-12-18
  • 2016-06-28
  • 1970-01-01
  • 2018-06-25
相关资源
最近更新 更多