ASP.NET OWIN OpenID Connect 不创建用户身份验证

swb*_*haw 6 c# owin owin-middleware openid-connect

我有一个 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);
                }
            }
        });
    }
Run Code Online (Sandbox Code Playgroud)

这是 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);
        }        
    }
Run Code Online (Sandbox Code Playgroud)

页面流程是这样的:

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 Set-Cookie: OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=yyyyyyyyy; 到期=格林威治标准时间 2019 年 4 月 22 日星期一 14:12:00;路径=/;仅Http

3) Auth provider,登录,然后302重定向到/LoginCallback

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

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

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

5) 返回Page.aspx,用户未认证;转到步骤 1

我已经做了一些调试,AuthorizationCodeReceived 在启动时触发,后端成功调用用户信息端点。我试图从该通知中调用 System.Web.HttpContext.Current.GetOwinContext().Authentication.SignIn() ,但这似乎没有任何作用。

在这一点上,我被困住了。为什么没有设置用户身份的认证cookie?这似乎应该是自动发生的。我应该自己手动创建吗?(如何手动创建身份验证 cookie 而不是默认方法?

编辑:在查看@ 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);
Run Code Online (Sandbox Code Playgroud)

Zax*_*xon 7

是的,我必须在 VB.Net 中进行概念验证,这有点痛苦。这是我的测试代码(即不是生产代码),它基于我看到的其他一些 C# 互联网示例:

\n\n
Imports System.Security.Claims\nImports System.Threading.Tasks\nImports IdentityModel\nImports IdentityModel.Client\nImports Microsoft.AspNet.Identity\nImports Microsoft.AspNet.Identity.Owin\nImports Microsoft.IdentityModel.Protocols.OpenIdConnect\nImports Microsoft.Owin\nImports Microsoft.Owin.Security\nImports Microsoft.Owin.Security.Cookies\nImports Microsoft.Owin.Security.Notifications\nImports Microsoft.Owin.Security.OAuth\nImports Microsoft.Owin.Security.OpenIdConnect\nImports Owin\n\nPartial Public Class Startup\n    Private Shared _oAuthOptions As OAuthAuthorizationServerOptions\n    Private Shared _publicClientId As String\n\n    Private Shared _clientId As String\n    Private Shared _clientSecret As String\n\n    \' Enable the application to use OAuthAuthorization. You can then secure your Web APIs\n    Shared Sub New()\n\n        _clientId = System.Configuration.ConfigurationManager.AppSettings("OAuth:ClientID").ToString()\n        _clientSecret = System.Configuration.ConfigurationManager.AppSettings("OAuth:SecretKey").ToString()\n\n        PublicClientId = _clientId\n\n        OAuthOptions = New OAuthAuthorizationServerOptions() With {\n            .TokenEndpointPath = New PathString("/Token"), \'New PathString("https://authtesteria.domain.com/as/token.oauth2"), \' \n            .AuthorizeEndpointPath = New PathString("/Account/Authorize"), \'New PathString("https://authtesteria.domain.com/as/authorization.oauth2"), \'\n            .Provider = New ApplicationOAuthProvider(PublicClientId),\n            .AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),\n            .AllowInsecureHttp = True\n        }\n    End Sub\n\n    Public Shared Property OAuthOptions() As OAuthAuthorizationServerOptions\n        Get\n            Return _oAuthOptions\n        End Get\n        Private Set\n            _oAuthOptions = Value\n        End Set\n    End Property\n\n    Public Shared Property PublicClientId() As String\n        Get\n            Return _publicClientId\n        End Get\n        Private Set\n            _publicClientId = Value\n        End Set\n    End Property\n\n    \' For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864\n    Public Sub ConfigureAuth(app As IAppBuilder)\n        \' Configure the db context, user manager and signin manager to use a single instance per request\n        app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)\n        app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)\n        app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)\n\n        \' Enable the application to use a cookie to store information for the signed in user\n        \' and to use a cookie to temporarily store inforation about a user logging in with a third party login provider\n        \' Configure the sign in cookie\n        \' OnValidateIdentity enables the application to validate the security stamp when the user logs in.\n        \' This is a security feature which is used when you change a password or add an external login to your account.\n        app.UseCookieAuthentication(New CookieAuthenticationOptions() With {\n            .AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,\n            .Provider = New CookieAuthenticationProvider() With {\n                .OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser)(\n                    validateInterval:=TimeSpan.FromMinutes(30),\n                    regenerateIdentity:=Function(manager, user) user.GenerateUserIdentityAsync(manager))},\n            .LoginPath = New PathString("/Account/Login")})\n\n\n        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)\n\n        \' Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.\n        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5))\n\n        \' Enables the application to remember the second login verification factor such as phone or email.\n        \' 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.\n        \' This is similar to the RememberMe option when you log in.\n        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie)\n\n        \' Enable the application to use bearer tokens to authenticate users\n        app.UseOAuthBearerTokens(OAuthOptions)\n\n        Dim controller As New AccountController()\n\n        \'Dim validator As OpenIdConnectProtocolValidator = New OpenIdConnectProtocolValidator()\n        \'validator.ShowPII = False\n\n        Dim oidcAuth As New Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions() With {\n            .ClientId = _clientId,\n            .ClientSecret = _clientSecret,\n            .Authority = "https://authtesteria.domain.com",\n            .Notifications = New Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationNotifications() With {\n                .RedirectToIdentityProvider = AddressOf OnRedirectToIdentityProvider,\n                .MessageReceived = AddressOf OnMessageReceived,\n                .SecurityTokenReceived = AddressOf OnSecurityTokenReceived,\n                .SecurityTokenValidated = AddressOf OnSecurityTokenValidated,\n                .AuthorizationCodeReceived = AddressOf OnAuthorizationCodeReceived,\n                .AuthenticationFailed = AddressOf OnAuthenticationFailed\n        }}\n        app.UseOpenIdConnectAuthentication(oidcAuth)\n\n    End Sub\n\n    Private Function OnRedirectToIdentityProvider(arg As RedirectToIdentityProviderNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task\n        Debug.WriteLine("*** RedirectToIdentityProvider")\n\n        If arg.ProtocolMessage.RequestType = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Logout Then\n            Dim idTokenHint = arg.OwinContext.Authentication.User.FindFirst("id_token")\n\n            If idTokenHint IsNot Nothing Then\n                arg.ProtocolMessage.IdTokenHint = idTokenHint.Value\n            End If\n        End If\n        Return Task.FromResult(0)\n    End Function\n\n    Private Function OnMessageReceived(arg As MessageReceivedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task\n        Debug.WriteLine("*** MessageReceived")\n        Return Task.FromResult(0)\n    End Function\n\n    Private Function OnAuthorizationCodeReceived(arg As AuthorizationCodeReceivedNotification) As Task\n        Debug.WriteLine("*** AuthorizationCodeReceived")\n        \'Upon successful sign in, get & cache a token if you want here\n        Return Task.FromResult(0)\n    End Function\n\n    Private Function OnAuthenticationFailed(arg As AuthenticationFailedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task\n        Debug.WriteLine("*** AuthenticationFailed")\n        Return Task.FromResult(0)\n    End Function\n\n    Private Function OnSecurityTokenReceived(arg As SecurityTokenReceivedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task\n        Debug.WriteLine("*** SecurityTokenReceived")\n        Return Task.FromResult(0)\n    End Function\n\n    Private Async Function OnSecurityTokenValidated(arg As SecurityTokenValidatedNotification(Of Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions)) As Task\n        Debug.WriteLine("*** SecurityTokenValidated")\n        \'Verify the user signing in should have access or not.  Here I just pass folk thru.\n        Dim nid = New ClaimsIdentity(\n              DefaultAuthenticationTypes.ApplicationCookie, \'arg.AuthenticationTicket.Identity.AuthenticationType,\n              ClaimTypes.GivenName,\n              ClaimTypes.Role)\n\n        Dim tokenClient = New TokenClient("https://authtesteria.domain.com/as/token.oauth2",\n             _clientId,\n             _clientSecret)\n\n        Dim tokenResponse = Await tokenClient.RequestAuthorizationCodeAsync(arg.ProtocolMessage.Code, arg.ProtocolMessage.RedirectUri)\n\n        \' get userinfo data\n        Dim userInfoClient = New IdentityModel.Client.UserInfoClient("https://authtesteria.domain.com/idp/userinfo.openid")\n\n        Dim userInfo = Await userInfoClient.GetAsync(tokenResponse.AccessToken)\n        userInfo.Claims.ToList().ForEach(Sub(ui) nid.AddClaim(New Claim(ui.Type, ui.Value)))\n\n        \'\' keep the id_token for logout\n        \'nid.AddClaim(New Claim("id_token", arg.ProtocolMessage.IdToken))\n\n        \'\' add access token for sample API\n        \'nid.AddClaim(New Claim("access_token", arg.ProtocolMessage.AccessToken))\n\n        \'\' keep track of access token expiration\n        \'nid.AddClaim(New Claim("expires_at", DateTimeOffset.Now.AddSeconds(Integer.Parse(arg.ProtocolMessage.ExpiresIn)).ToString()))\n\n        \'\' add some other app specific claim\n        \'nid.AddClaim(New Claim("app_specific", "some data"))\n\n        nid.AddClaim(New Claim(ClaimTypes.Role, "group1"))\n\n        arg.AuthenticationTicket = New AuthenticationTicket(nid, arg.AuthenticationTicket.Properties)\n        arg.AuthenticationTicket.Properties.RedirectUri = HttpContext.Current.Session("PageRedirect").ToString() \n    End Function\nEnd Class\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我像这样触发登录:

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

我们有一些不同之处:

\n\n
    \n
  1. 我使用 OnSecurityTokenValidated 但我不确定这是否重要
  2. \n
  3. 我用当前页面的 Request.Url 填充会话变量,
  4. \n
  5. 然后在 OnSecurityTokenValidated\ 的通知参数的启动中利用它: arg.AuthenticationTicket.Properties.RedirectUri = \xe2\x80\xa6 (请参阅我的代码)。
  6. \n
\n\n

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

\n