登录IdentityServer4时无效的访问令牌/遗失声明

时间:2018-09-21 00:24:33

标签: oauth-2.0 asp.net-core-2.0 identityserver4 openid-connect

我有一个标准的.NET Core 2.1(MVC和API)和Identity Server 4项目设置。 我使用的是参考令牌,而不是jwt令牌。

方案如下:

  • 浏览到我的应用程序
  • 已重定向到Identity Server
  • 输入有效的有效凭据
  • 使用所有声明(角色)重定向到应用程序,并正确访问应用程序和API

等待不确定的时间(我认为是一个小时,我没有确切的时间)

  • 浏览到我的应用程序
  • 已重定向到Identity Server
  • 我仍然登录了IDP ,因此我被立即重定向回我的 应用
  • 目前,已登录的.NET用户缺少声明(角色),并且无法再访问API

如果我删除所有应用程序cookie,也会发生相同的结果

在我看来,访问令牌已过期。如何处理这种情况?我仍然登录到IDP,并且中间件自动将我登录到我的应用程序,但是,访问令牌已过期(?)并且缺少声明。

这与引用标记的使用有关吗?

我正在研究大量的线索和文章,对此场景有任何指导和/或解决方案?

编辑:看来我的访问令牌有效。我已将问题缩小到缺少的用户个人资料数据。具体来说,角色声明。

当我同时清除应用程序和IDP Cookie时,一切正常。但是,在“ x”(1小时?)时间段之后,当我尝试刷新或访问该应用程序时,我被重定向到IDP,然后又回到该应用程序。

到那时,我有一个有效且经过身份验证的用户,但是,我错过了所有角色要求。

在这种情况下,如何配置AddOpenIdConnect中间件以获取缺少的声明?

我想在OnUserInformationReceived事件中,我可以检查是否缺少“角色”声明,如果丢失,则调用UserInfoEndpoint ...这似乎是一个非常奇怪的工作流程。尤其是因为“新”登录,“角色”声明又回来了。 (注意:在错误情况下,我确实看到角色声明从上下文中丢失了。)

这是我的客户端应用程序配置:

services.AddAuthentication(authOpts =>
        {
            authOpts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            authOpts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts => { })
        .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, openIdOpts =>
        {
            openIdOpts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            openIdOpts.Authority = settings.IDP.Authority;
            openIdOpts.ClientId = settings.IDP.ClientId;
            openIdOpts.ClientSecret = settings.IDP.ClientSecret;
            openIdOpts.ResponseType = settings.IDP.ResponseType;

            openIdOpts.GetClaimsFromUserInfoEndpoint = true;
            openIdOpts.RequireHttpsMetadata = false;
            openIdOpts.SaveTokens = true;
            openIdOpts.ResponseMode = "form_post";

            openIdOpts.Scope.Clear();
            settings.IDP.Scope.ForEach(s => openIdOpts.Scope.Add(s));

            // https://leastprivilege.com/2017/11/15/missing-claims-in-the-asp-net-core-2-openid-connect-handler/
            // https://github.com/aspnet/Security/issues/1449
            // https://github.com/IdentityServer/IdentityServer4/issues/1786

            // Add Claim Mappings
            openIdOpts.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username"); /* SID alias */
            openIdOpts.ClaimActions.MapJsonKey("role", "role", "role");

            openIdOpts.TokenValidationParameters = new TokenValidationParameters
            {
                ValidAudience = settings.IDP.ClientId,
                ValidIssuer = settings.IDP.Authority,
                NameClaimType = "name",
                RoleClaimType = "role"
            };

            openIdOpts.Events = new OpenIdConnectEvents
            {
                OnUserInformationReceived = context =>
                {
                    Log.Info("Recieved user info from IDP.");
                    // check for missing roles?  they are here on a fresh login but missing
                    // after x amount of time (1 hour?)
                    return Task.CompletedTask;
                },
                OnRedirectToIdentityProvider = context =>
                {
                    Log.Info("Redirecting to identity provider.");
                    return Task.CompletedTask;
                },
                OnTokenValidated = context =>
                    {
                        Log.Debug("OnTokenValidated");
                        // this addressed the scenario where the Identity Server validates a user however that user does not
                        // exist in the currently configured source system.
                        // Can happen if there is a configuration mismatch between the local SID system and the IDP Client
                        var validUser = false;
                        int uid = 0;
                        var identity = context.Principal?.Identity as ClaimsIdentity;

                        if (identity != null)
                        {
                            var sub = identity.Claims.FirstOrDefault(c => c.Type == "sub");

                            Log.Debug($"    Validating sub '{sub.Value}'");

                            if (sub != null && !string.IsNullOrWhiteSpace(sub.Value))
                            {

                                if (Int32.TryParse(sub.Value, out uid))
                                {
                                    using (var configSvc = ApiServiceHelper.GetAdminService(settings))
                                    {
                                        try
                                        {
                                            var usr = configSvc.EaiUser.GetByID(uid);

                                            if (usr != null && usr.ID.GetValueOrDefault(0) > 0)
                                                validUser = true;
                                        }
                                        catch { }
                                    }
                                }
                            }

                            Log.Debug($"    Validated sub '{sub.Value}'");
                        }

                        if (!validUser)
                        {
                            // uhhh, does this work?  Logout?
                            // TODO: test!
                            Log.Warn($"Unable to validate user is SID for ({uid}).  Redirecting to '/Home/Logout'");
                            context.Response.Redirect("/Home/Logout?msg=User not validated in source system");
                            context.HandleResponse();
                        }

                        return Task.CompletedTask;
                    },
                OnTicketReceived = context =>
                {
                    // TODO: Is this necessary?
                    // added the below code because I thought my application access_token was expired
                    // however it turns out I'm actually misisng the role claims when I come back to the
                    // application from the IDP after about an hour
                    if (context.Properties != null &&
                        context.Properties.Items != null)
                    {
                        DateTime expiresAt = System.DateTime.MinValue;

                        foreach (var p in context.Properties.Items)
                        {
                            if (p.Key == ".Token.expires_at")
                            {
                                DateTime.TryParse(p.Value, null, DateTimeStyles.AdjustToUniversal, out expiresAt);
                                break;
                            }
                        }

                        if (expiresAt != DateTime.MinValue &&
                            expiresAt != DateTime.MaxValue)
                        {
                            // I did this to synch the .NET cookie timeout with the IDP access token timeout?
                            // This somewhat concerns me becuase I thought that part should be done auto-magically already
                            // I mean, refresh token?
                            context.Properties.IsPersistent = true;
                            context.Properties.ExpiresUtc = expiresAt;
                        }
                    }

                    return Task.CompletedTask;
                }
            };
        });

1 个答案:

答案 0 :(得分:0)

对不起大家,看来我找到了问题的根源。

我总失败了:(。

我的Identity Server实现中的ProfileService中有一个错误,该错误导致在所有情况下都不会返回角色

哼,谢谢!