Identity Server 4登录后重定向仅在Chrome中不起作用

时间:2020-03-19 12:12:17

标签: .net-core identityserver4

我使用身份服务器4称它为.net核心3.1上运行的“身份验证服务器”。 重定向到auth-server后提供了角度的应用程序请求身份验证,并提供提交登录信息的凭据,但未重定向回客户端应用程序。 该问题仅在Chrome浏览器中有效(firefox和edge正常工作) 我可以看到重定向请求-Request-Url 但它只是返回登录页面 客户配置:

public static IEnumerable<Client> GetClients()
{
    return new List<Client>(){
            new Client() {
                             RequireConsent =false,
                             RequireClientSecret = false,
                             ClientId = "takbull-clientapp-dev",
                             ClientName = "Takbull Client",
                             AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials,
                             AllowedScopes = new List<string> 
                             {
                              IdentityServerConstants.StandardScopes.OpenId,
                              IdentityServerConstants.StandardScopes.Email,
                              IdentityServerConstants.StandardScopes.Profile,
                              "takbull",
                              "takbull.api" 
                             },
                             // where to redirect to after login
                             RedirectUris = new List<string>()
                             {
                                 "http://localhost:4200/auth-callback/",
                                 "http://localhost:4200/silent-refresh.html",
                             },
                             //TODO: Add Production URL
                             // where to redirect to after logout
                             PostLogoutRedirectUris =new List<string>() 
                             {
                                 "http://localhost:4200"
                             },
                             AllowedCorsOrigins = {"http://localhost:4200"},
                             AllowAccessTokensViaBrowser = true,
                             AccessTokenLifetime = 3600,
                             AlwaysIncludeUserClaimsInIdToken = true
                         },
        };
    }

登录代码:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
    // check if we are in the context of an authorization request
    var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);

    // the user clicked the "cancel" button
    if (button != "login")
    {
        if (context != null)
        {
            // if the user cancels, send a result back into IdentityServer as if they 
            // denied the consent (even if this client does not require consent).
            // this will send back an access denied OIDC error response to the client.
            await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);

            // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
            if (await _clientStore.IsPkceClientAsync(context.ClientId))
            {
                // if the client is PKCE then we assume it's native, so this change in how to
                // return the response is for better UX for the end user.
                return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
             }

             return Redirect(model.ReturnUrl);
         }
         else
         {
            // since we don't have a valid context, then we just go back to the home page
            return Redirect("~/");
         }
     }

     if (ModelState.IsValid)
     {
         // validate username/password against in-memory store
         var ValidResp = await _users.ValidateCredentials(model.Username, model.Password);
         if (ValidResp.LogInStatus == LogInStatus.Success)
         {
             var user = _users.FindByUsername(model.Username);
             //await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username));
             await _events.RaiseAsync(new UserLoginSuccessEvent(user.Email, user.UserId.ToString(), user.Email));

             // only set explicit expiration here if user chooses "remember me". 
             // otherwise we rely upon expiration configured in cookie middleware.
             AuthenticationProperties props = null;
             if (AccountOptions.AllowRememberLogin && model.RememberLogin)
             {
                 props = new AuthenticationProperties
                 {
                    IsPersistent = true,
                    ExpiresUtc = DateTimeOffset.Now.Add(AccountOptions.RememberMeLoginDuration)
                 };
             };

             // issue authentication cookie with subject ID and username
             //await HttpContext.SignInAsync(user.SubjectId, user.Username, props);
             // issue authentication cookie with subject ID and username
             await HttpContext.SignInAsync(user.UserId.ToString(), user.FirstName + " " + user.LastName, props, _users.GetClaims(user).ToArray());

             if (context != null)
             {
                 if (await _clientStore.IsPkceClientAsync(context.ClientId))
                 {
                     // if the client is PKCE then we assume it's native, so this change in how to
                     // return the response is for better UX for the end user.
                     return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
                 }

                 // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
                 return Redirect(model.ReturnUrl);
             }

             // request for a local page
             if (Url.IsLocalUrl(model.ReturnUrl))
             {
                 return Redirect(model.ReturnUrl);
             }
             else if (string.IsNullOrEmpty(model.ReturnUrl))
             {
                 return Redirect("~/");
             }
             else
             {
                 // user might have clicked on a malicious link - should be logged
                 throw new Exception("invalid return URL");
             }
         }

         await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, ValidResp.ResponseDescription));
         ModelState.AddModelError(string.Empty, ValidResp.ResponseDescription);
     }

     // something went wrong, show form with error
     var vm = await BuildLoginViewModelAsync(model);
     return View(vm);
}

5 个答案:

答案 0 :(得分:8)

.NET Core 2.2上的IdentityServer4也有类似的问题。您的问题可能与新的浏览器版本(例如Chrome或Firefox)中的重大更改有关:

https://docs.microsoft.com/en-gb/dotnet/core/compatibility/3.0-3.1#http-browser-samesite-changes-impact-authentication

对我来说,可行的解决方案是完全关闭cookie的 SameSite 配置。此处介绍了.NET Core 2.2的这种可能性:

https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1

((如果您的解决方案在.NET Core 3.1上,则在下面的代码中,而不是使用(SameSiteMode)(-1),而应使用 SameSiteMode.Unspecified

FIX: 在 IdentityServerBuilder 创建后的 ConfigureServices 方法中的 Startup.cs 文件中...

var builder = services.AddIdentityServer(options =>
            {....});

...我添加了以下配置更改:

builder.Services.ConfigureExternalCookie(options => {
   options.Cookie.IsEssential = true;
      options.Cookie.SameSite = (SameSiteMode)(-1); //SameSiteMode.Unspecified in .NET Core 3.1
   });

builder.Services.ConfigureApplicationCookie(options => {
   options.Cookie.IsEssential = true;
      options.Cookie.SameSite = (SameSiteMode)(-1); //SameSiteMode.Unspecified in .NET Core 3.1
});

答案 1 :(得分:5)

我最近在chrome和edge上遇到问题,但几个月前还只有chrome。 因此,对于我来说,.Net Core 3和IdentityServer4版本3.1.2通过将以下代码添加到startup.cs开始工作:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ...)
    {
            app.UseCookiePolicy(new CookiePolicyOptions
            {
                MinimumSameSitePolicy = SameSiteMode.Lax
            });

注意:请确保将此策略添加到Configure方法的开头,而不是在启动时结束。否则cs无效。

答案 2 :(得分:4)

您将在Google Chrome中进入控制台警告,并且您的身份服务器无法重定向到适用于Chrome 80版的客户端应用。

与资源at相关联的cookie设置为SameSite = None,但未设置安全。它已被阻止,因为Chrome现在仅发送标记为SameSite = None的cookie(如果它们也被标记为Secure)。您可以在Application> Storage> Cookies下的开发人员工具中查看cookie,并在https://www.chromestatus.com/feature/5633521622188032上查看更多详细信息。

要解决此问题,您需要进行以下链接中提及的更改-

https://www.thinktecture.com/en/identity/samesite/prepare-your-identityserver/

注意:对于.Net Core 2.2,设置SameSite =(SameSiteMode)(-1),对于.Net Core 3.0或更高版本,设置SameSite = SameSiteMode.Unspecified

此外,对于Chrome 80版本,请添加此额外条件-

 if (userAgent.Contains("Chrome/8"))
 {
     return true;
 }

答案 3 :(得分:2)

设置OpenidConnectionOptions对我有用

CorrelationCookie.SameSite = SameSiteMode.Lax;
NonceCookie.SameSite = SameSiteMode.Lax;

答案 4 :(得分:0)

您应该在 Startup.cs 的 ConfigureServices 范围内添加以下代码

services.AddAuthentication("MyCookie")
                .AddCookie("MyCookie", options =>
                {
                    options.ExpireTimeSpan = new TimeSpan(24, 0, 0);
                });