如何处理使用 IdentityServer3 作为身份验证服务器的帖子

How to handle posts with IdentityServer3 as authentication server

TL;DR

如何在 ASP.NET MVC 项目(表单,jQuery,axios)中使用 IdentityServer3 作为身份验证服务器来 POST 数据。另外,要使用什么流程才能完成这项工作?

我正在经历的事情

我有一个可用的 IdentityServer3 实例。我还有一个 ASP.NET MVC 项目。使用混合流,因为我必须将用户的令牌传递给其他服务。身份验证本身有效 - 当页面仅使用 GET 时。即使经过身份验证的用户的令牌已过期,后台也会将请求重定向到 auth。服务器,用户可以继续它的工作,而无需要求用户再次登录。 (据我所知,混合流程可以使用刷新令牌,所以我认为这就是它可以重新验证用户身份的方式。即使 HttpContext.Current.User.Identity.IsAuthenticated=false

出于测试目的,我在身份验证中将 AccessTokenLifetimeAuthorizationCodeLifetimeIdentityTokenLifetime 值设置为 5 秒。服务器。据我所知,刷新令牌的过期时间以天为单位,我没有更改默认值。

但是当我尝试使用 POST 时,事情变得 "ugly"。

startup.cs

  const string authType = "Cookies";

  // resetting Microsoft's default mapper
  JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

  // ensure, that the MVC anti forgery key engine will use our "custom" user id
  AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";

  app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
  {
    AuthenticationType = authType
  });

  app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
  {
    ClientId = clientId,
    RedirectUri = adminUri,
    PostLogoutRedirectUri = adminUri,
    Authority = idServerIdentityEndpoint,
    SignInAsAuthenticationType = authType,
    ResponseType = "code id_token",
    Scope = "openid profile roles email offline_access",

    Notifications = new OpenIdConnectAuthenticationNotifications
    {
      #region Handle automatic redirect (on logout)

      RedirectToIdentityProvider = async n =>
      {
        // if signing out, add the id_token_hint
        if (n.ProtocolMessage.RequestType ==
              OpenIdConnectRequestType.LogoutRequest)
        {
          var token = n.OwinContext.Authentication.User.FindFirst(idTokenName);
          if (token != null)
          {
            var idTokenHint =
              token.Value;
            n.ProtocolMessage.IdTokenHint = idTokenHint;
          }
        }
      },

      #endregion

      AuthorizationCodeReceived = async n =>
      {
        System.Diagnostics.Debug.Print("AuthorizationCodeReceived " + n.ProtocolMessage.ToString());

        // fetch the identity from authentication response
        var identity = n.AuthenticationTicket.Identity;

        // exchange the "code" token for access_token, id_token, refresh_token, using the client secret
        var requestResponse = await OidcClient.CallTokenEndpointAsync(
          new Uri(idServerTokenEndpoint),
          new Uri(adminUri),
          n.Code,
          clientId,
          clientSecret
        );

        // fetch tokens from the exchange response
        identity.AddClaims(new []
        {
          new Claim("access_token", requestResponse.AccessToken), 
          new Claim("id_token", requestResponse.IdentityToken), 
          new Claim("refresh_token", requestResponse.RefreshToken)
        });

        // store the refresh_token in the session, as the user might be logged out, when the authorization attribute is executed
        // see OrganicaAuthorize.cs
        HttpContext.Current.Session["refresh_token"] = requestResponse.RefreshToken;

        // get the userinfo from the openId endpoint
        // this actually retreives all the claims, but using the normal access token
        var userInfo = await EndpointAndTokenHelper.CallUserInfoEndpoint(idServerUserInfoEndpoint, requestResponse.AccessToken); // todo: userinfo

        if (userInfo == null) throw new Exception("Could not retreive user information from identity server.");

        #region Extract individual claims

        // extract claims we are interested in
        var nameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Name,
          userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Name)); // full name
        var givenNameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName,
          userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName)); // given name
        var familyNameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName,
          userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName)); // family name
        var emailClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Email,
          userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Email)); // email
        var subClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject,
          userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject)); // userid

        #endregion

        #region Extract roles
        List<string> roles;
        try
        {
          roles = userInfo.Value<JArray>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role).Select(r => r.ToString()).ToList();
        }
        catch (InvalidCastException) // if there is only 1 item
        {
          roles = new List<string> { userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role) };
        }
        #endregion

        // attach the claims we just extracted
        identity.AddClaims(new[] { nameClaim, givenNameClaim, familyNameClaim, subClaim, emailClaim });
        // attach roles
        identity.AddClaims(roles.Select(r => new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role, r.ToString())));

        // update the return value of the SecurityTokenValidated method (this method...)
        n.AuthenticationTicket = new AuthenticationTicket(
          identity,
          n.AuthenticationTicket.Properties);
      },
      AuthenticationFailed = async n => 
      {
        System.Diagnostics.Debug.Print("AuthenticationFailed " + n.Exception.ToString());
      },
      MessageReceived = async n =>
      {
        System.Diagnostics.Debug.Print("MessageReceived " + n.State.ToString());
      },
      SecurityTokenReceived = async n =>
      {
        System.Diagnostics.Debug.Print("SecurityTokenReceived " + n.State.ToString());
      },
      SecurityTokenValidated = async n =>
      {
        System.Diagnostics.Debug.Print("SecurityTokenValidated " + n.State.ToString());
      }
    }
  });

您是否在MVC应用中配置了cookie认证中间件?在使用身份服务器进行身份验证后,应设置一个身份验证 cookie。当设置了身份验证 cookie 并且在 cookie expires/deleted.

之前不会发生有效的 IdentityServer 重定向

更新 1:

好的,我误解了问题。当会话超时时重定向到身份服务器是合乎逻辑的。它不适用于 post 负载。您可以尝试执行以下操作。

  1. 如果请求是正常的 post,再次将用户重定向到表单 填写页面。
  2. 如果请求是 ajax post、return 未经授权的结果并且基于 该响应从 javascript.
  3. 刷​​新页面

无论如何,我认为您无法保留 posted 数据,除非您为此设计自己的解决方案。 (例如,将数据存储在本地)。

但如果您仔细决定身份服务器的会话超时和您的应用程序的会话超时,您也许能够完全避免这种情况。

OpenIdConnectAuthenticationOptions 中设置 UseTokenLifetime = false 这将断开身份令牌生命周期和 cookie 会话生命周期之间的联系。

CookieAuthenticationOptions做滑动过期 SlidingExpiration = true, ExpireTimeSpan = TimeSpan.FromMinutes(50),

现在您可以控制应用程序的会话生命周期。调整它以满足您的需求和安全考虑。