首页 > 解决方案 > 在通过 IdentityServer4 自动登录期间未分配会话 ID (sid),这是什么原因?

问题描述

问题

第一个问题,是什么决定了是否sid从身份服务器发出声明?

第二个问题,我什至需要一个sid?我目前包含它,因为它在样本中。

背景故事

我有一个使用 IdentityServer4 进行身份验证的网站和一个不使用的网站。我拼凑了一个解决方案,允许用户登录到 non-identityserver4 站点并单击使用一次性访问代码的链接自动登录到 identityserver4 站点。sid从非身份服务器站点传输时,除了声明没有从身份服务器传递到身份服务器保护的站点之外,一切似乎都正常工作。如果我直接登录到 identityserver4 安全站点,sid 则它包含在声明中。代码改编自注册后自动登录和/或模拟工作流程的示例。

这是代码:

identityserver4 中的一次代码登录过程

public class CustomAuthorizeInteractionResponseGenerator : AuthorizeInteractionResponseGenerator
{
...
    //https://stackoverflow.com/a/51466043/391994
    public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request,
    ConsentResponse consent = null)
    {
        string oneTimeAccessToken = request.GetAcrValues().FirstOrDefault(x => x.Split(':')[0] == "otac");

        string clientId = request.ClientId;

        //handle auto login handoff 
        if (!string.IsNullOrWhiteSpace(oneTimeAccessToken))
        {
            //https://benfoster.io/blog/identity-server-post-registration-sign-in/
            oneTimeAccessToken = oneTimeAccessToken.Split(':')[1];
            OneTimeCodeContract details = await GetOTACFromDatabase(oneTimeAccessToken);

            if (details.IsValid)
            {
                UserFormContract user = await GetPersonUserFromDatabase(details.PersonId);

                if (user != null)
                {
                    string subjectId = await GetClientSubjectIdAsync(clientId, user.AdUsername);

                    var iduser = new IdentityServerUser(subjectId)
                    {
                        DisplayName = user.AdUsername,
                        AuthenticationTime = DateTime.Now,
                        IdentityProvider = "local",
                    };

                    request.Subject = iduser.CreatePrincipal();

                    //revoke token
                    bool? success = await InvalidateTokenInDatabase(oneTimeAccessToken);

                    if (success.HasValue && !success.Value)
                    {
                        Log.Debug($"Revoke failed for {oneTimeAccessToken} it should expire at {details.ExpirationDate}");
                    }

                    //https://stackoverflow.com/a/56237859/391994
                    //sign them in
                    await _httpContextAccessor.HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, request.Subject, null);

                    return new InteractionResponse
                    {
                        IsLogin = false,
                        IsConsent = false,
                    };
                }
            }
        }

        return await base.ProcessInteractionAsync(request, consent);
    }
}

直接登录到 identityserver4 安全站点时的正常登录流程(来自示例)

public class AccountController : Controller
{
    /// <summary>
    /// Handle postback from username/password login
    /// </summary>
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(LoginInputModel model)
    {
        Log.Information($"login request from: {Request.HttpContext.Connection.RemoteIpAddress.ToString()}");

        if (ModelState.IsValid)
        {
            // validate username/password against in-memory store
            if (await _userRepository.ValidateCredentialsAsync(model.Username, model.Password))
            {
                AuthenticationProperties props = null;
                // only set explicit expiration here if persistent. 
                // otherwise we reply upon expiration configured in cookie middleware.
                if (AccountOptions.AllowRememberLogin && model.RememberLogin)
                {
                    props = new AuthenticationProperties
                    {
                        IsPersistent = true,
                        ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
                    };
                };

                var clientId = await _account.GetClientIdAsync(model.ReturnUrl);

                // issue authentication cookie with subject ID and username
                var user = await _userRepository.FindByUsernameAsync(model.Username, clientId);

                var iduser = new IdentityServerUser(user.SubjectId)
                {
                    DisplayName = user.UserName
                };

                await HttpContext.SignInAsync(iduser, props);

                // make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
                if (_interaction.IsValidReturnUrl(model.ReturnUrl))
                {
                    return Redirect(model.ReturnUrl);
                }

                return Redirect("~/");
            }

            ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
        }

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

Identityserver4 安全站点中的 AuthorizationCodeReceived

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = async n =>
                {
                    // use the code to get the access and refresh token
                    var tokenClient = new TokenClient(
                        tokenEndpoint,
                        electionClientId,
                        electionClientSecret);

                    var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                        n.Code, n.RedirectUri);

                    if (tokenResponse.IsError)
                    {
                        throw new Exception(tokenResponse.Error);
                    }

                    // use the access token to retrieve claims from userinfo
                    var userInfoClient = new UserInfoClient(
                    new Uri(userInfoEndpoint).ToString());

                    var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

                    Claim subject = userInfoResponse.Claims.Where(x => x.Type == "sub").FirstOrDefault();

                    // create new identity
                    var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);

                    id.AddClaims(GetRoles(subject.Value, tokenClient, apiResourceScope, apiBasePath));

                    var transformedClaims = StartupHelper.TransformClaims(userInfoResponse.Claims);

                    id.AddClaims(transformedClaims);

                    id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                    id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                    id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                    id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
    THIS FAILS ->   id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                    n.AuthenticationTicket = new AuthenticationTicket(
                        new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                        n.AuthenticationTicket.Properties);
                },
            }
        });
    }
}

如果您不想向上滚动,请再次提问

第一个问题,是什么决定了是否sid从身份服务器发出声明?

第二个问题,我什至需要一个sid?我目前包含它,因为它在样本中。

标签: identityserver4

解决方案


推荐阅读