不支持身份服务器响应类型:code + id_token

时间:2018-09-12 03:00:20

标签: c# .net owin identityserver4 openid-connect

我将身份服务器4项目作为服务器,并将asp.net mvc 5作为客户端。一切正常,除了一个偶然的问题:不支持响应类型:code + id_token

在通过生产日志进行调查期间,我发现授权URL偶尔被编码两次,因此找不到根本原因并进行重现。我猜这是Microsoft.Owin.Security.OpenIdConnect的内部行为,

未登录的用户访问受保护的资源时,会自动生成授权的URL。

在我的mvc客户端上,首先将“代码id_token”编码为“ code + id_token”,然后将“代码+ id_token”编码为“ code%2Bid_token”。

在我的身份服务器方面,“ code%2Bid_token”被解码为“ code + id_token”,从而发生验证错误。

以下是我的日志:

INFO  11:54:35 Request starting HTTP/1.1 GET http://login.example.com/connect/authorize?client_id=gjcf_mvc&nonce=636709820590066816.ZWNlNzJmOGQtMGFhNC00NzVkLTllNzktNmE5NTIzN2EzNDE3NThhMmI2OGYtODI5Mi00OTE2LTgzN2MtNGFkZWUzODQ4Nzlk&redirect_uri=https%3A%2F%2Fwww.example.com%2Fsignin-oidc&response_mode=form_post&response_type=code%2Bid_token&scope=openid%2Bprofile%2Bapi1%2BGjcfApi%2Boffline_access&state=OpenIdConnect.AuthenticationProperties 

INFO  11:54:35 Invoking IdentityServer endpoint: IdentityServer4.Endpoints.AuthorizeEndpoint for /connect/authorize

ERROR 11:54:35 Response type not supported: code+id_token
{
  "ClientId": "gjcf_mvc",
  "ClientName": "gjcf_mvc_name",
  "RedirectUri": "https://www.example.com/signin-oidc",
  "AllowedRedirectUris": [
    "https://www.example.com/signin-oidc"
  ],
  "SubjectId": "anonymous",
  "RequestedScopes": "",
  "State": "OpenIdConnect.AuthenticationProperties=5USuW-uf3wCad1ap9VCDDCE6bTKr1mUMZob-yI_vBUsAFqx_7oLv-0f3rTApD5_6NjVf3siQsJKg9cH4T7YA6ra2B_6_Yooq_S0rJW2L3I4a13Gg5DpcESjg8gb4MQSysOm_xLjgXa96gpGN0tTwNmnb6dB6S3c3ttIDPt_JWCI0qHclfprE_RlO4RlY3LqsI3YhGznHUXM9UW-x38KB9vUtdfulXYrWRko35cQmezI3QAIXqOCt_d7qLgL5WBeNRRk8I0QrbfrmhTwwtS1fTBi5vUPujBPi9L14mCeKPbNZIm5w4oqZOznjBhw0k5v2",
  "Raw": {
    "client_id": "gjcf_mvc",
    "nonce": "636709820590066816.ZWNlNzJmOGQtMGFhNC00NzVkLTllNzktNmE5NTIzN2EzNDE3NThhMmI2OGYtODI5Mi00OTE2LTgzN2MtNGFkZWUzODQ4Nzlk",
    "redirect_uri": "https://www.example.com/signin-oidc",
    "response_mode": "form_post",
    "response_type": "code+id_token",
    "scope": "openid+profile+api1+GjcfApi+offline_access",
    "state": "OpenIdConnect.AuthenticationProperties"
  }
}

以下代码在asp.net mvc启动中:

app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AuthenticationType = "Cookies",
                    Provider = new CookieAuthenticationProvider
                    {
                        OnResponseSignIn = context =>
                        {
                            context.Properties.AllowRefresh = true;
                            context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(40);
                        }
                    }
                });

                GjcfOpenIdConnectConfiguration conf = GjcfOpenIdConnectConfiguration.Instance;

                app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
                {
                    ClientId = conf.ClientId,
                    ClientSecret = conf.ClientSecret,
                    Authority = conf.Authority,
                    RedirectUri = conf.RedirectUri,
                    PostLogoutRedirectUri = conf.PostLogoutRedirectUri,

                    ResponseType = conf.ResponseType,
                    Scope = conf.Scope,
                    SignInAsAuthenticationType = "Cookies",


                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthorizationCodeReceived = async n =>
                        {
                            // use the code to get the access and refresh token
                            var tokenClient = new TokenClient(
                                conf.TokenAddress,
                                conf.ClientId,
                                conf.ClientSecret);

                            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(conf.UserInfoAddress),
                                tokenResponse.AccessToken);

                            var userInfoResponse = await userInfoClient.GetAsync();

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

                            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));
                            id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
                            id.AddClaim(new Claim(ClaimTypes.NameIdentifier,
                                n.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value));
                            id.AddClaim(new Claim(
                                "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
                                n.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value));

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

                        AuthenticationFailed = (context) =>
                        {
                            if (context.Exception.Message.StartsWith("OICE_20004") || context.Exception.Message.Contains("IDX10311"))
                            {
                                context.SkipToNextMiddleware();
                                return Task.FromResult(0);
                            }
                            return Task.FromResult(0);
                        },

                        RedirectToIdentityProvider = n =>
                        {                                    
                            // if signing out, add the id_token_hint
                            if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                            {
                                var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                                if (idTokenHint != null)
                                {
                                    n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                                }

                            }

                            return Task.FromResult(0);
                        }
                    }
                });

当用户访问标记为Authorize属性的操作时,将生成授权URL。

[Authorize]
        public ActionResult IdentityServerJump(string returnUrl)
        {
            if (!string.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }

1 个答案:

答案 0 :(得分:0)

尽管您未提供代码,但以下部分存在问题

  

在我的mvc客户端上,“代码id_token”首先被编码为“ code + id_token” ,然后“代码+ id_token”被编码为“ code%2Bid_token”

不确定为什么要进行双重编码。从IS4(身份服务器)端,它将在不知道查询段是双重编码的情况下进行URL解码。

对此的更正是使用单个编码。根据OpenID Connect规范,坚持使用 空格的%20 编码,不要像现在这样加倍编码。另外,我也欢迎您在this answer

上阅读 + %20