由于未授权客户端错误,无法获取令牌

Posted

技术标签:

【中文标题】由于未授权客户端错误,无法获取令牌【英文标题】:Can't obtain token due to unauthorized_client error 【发布时间】:2019-05-18 03:20:00 【问题描述】:

我正在编写一个关于如何使用 Postman 从 IDS4 获取令牌的演示。

密码令牌请求来自IDS4's page。

[HttpGet("token")]
public IActionResult GetToken([FromHeader] string user, [FromHeader] string pass)

  string tokenEndpoint = "https://localhost:44300/connect/token";
  HttpClient client = new HttpClient();

  Task<TokenResponse> tokenResponse = 
    client.RequestPasswordTokenAsync(new PasswordTokenRequest
  
    Address = tokenEndpoint,

    ClientId = "client",
    ClientSecret = "client_secret",
    Scope = "MemberApi.full",

    UserName = user,
    Password = pass
  );
  TokenResponse toko = tokenResponse.Result;

  if (toko.IsError)
    return Ok(toko.Error);
  return Ok(toko.AccessToken;

客户端设置如下。

private static IEnumerable<Client> GetClients => new[]

  ...
  new Client
  
    ClientId = "client",
    ClientSecrets =  new Secret("client_secret".Sha256()) ,
    ClientName = "Client",

    AllowedGrantTypes = GrantTypes.Implicit,
    AllowAccessTokensViaBrowser = true,

    RedirectUris =  "http://localhost:5000/security/credentials" ,
    PostLogoutRedirectUris =  "http://localhost:5000/index.html" ,
    AllowedCorsOrigins =  "http://localhost:5000", "https://localhost:44300" ,

    AllowedScopes =
    
      IdentityServerConstants.StandardScopes.OpenId,
      IdentityServerConstants.StandardScopes.Profile,
      IdentityServerConstants.StandardScopes.Email,
      "MemberApi",
      "MemberApi.full",
      "MemberApi.limited"
    
  
;

API 资源设置如下所示。

private static IEnumerable<ApiResource> GetApis => new[]

  new ApiResource
  
    Name = "MemberApi",
    DisplayName = "Members' API",
    ApiSecrets = new Secret("MemberSecret".Sha256()),
    UserClaims = JwtClaimTypes.Name, JwtClaimTypes.Email, JwtClaimTypes.Role,
    Scopes = new Scope("MemberApi.full"), new Scope("MemberApi.limited")
  
;

据我所知,我遵循了文档中的建议。我也尝试与示例进行比较。尽管如此,我还是遇到了 unauthorized_client 的错误。我会错过什么?

【问题讨论】:

【参考方案1】:

此流程中不允许客户端请求:

AllowedGrantTypes = GrantTypes.Implicit

忘记 client.RequestPasswordTokenAsync。你不需要它,你也不能使用它。在隐式流程中,只有用户知道密码。对客户来说是遥不可及的。

假设 IdentityServer 在一个域上运行:https://idp.mydomain.com,而客户端在其他地方运行:https://mvc.mydomain.com

当用户点击 mvc 客户端上的安全页面时,用户被路由到用户登录的 IdentityServer。用户在那里输入凭据,如果成功,用户将返回到客户端作为一个已知的身份。

根据流程,客户端最终至少会获得一个访问令牌。这个令牌很重要,因为它允许客户端代表用户访问资源。就像一张入场券。

根据访问令牌,资源现在知道谁想要访问该资源。访问令牌有一个声明可以做出这种区分,即“子”声明。如果没有此声明,客户端将无法访问此流程中的资源。

在您的配置中,允许客户端访问“MemberApi”范围,但它需要用户同意才能真正访问资源。


如果您想检索令牌,请从最简单的流程开始,client credentials 流程。

这就是根本没有用户的流程。客户端(如在软件中)可以使用 clientid + secret 登录。如果配置正确,这将产生一个访问令牌。

现在客户端无需任何用户交互即可访问资源。由于没有用户,身份令牌不可用。缺少“子”声明。此流程不支持刷新令牌,它不需要它。客户端可以使用凭据请求新令牌。


如果您想了解refresh token 的工作原理,请在hybrid flow 中用户登录,此外(如果配置了 scope=offline)会返回一个刷新令牌。

由于访问令牌仅在短时间内有效(取决于到期时间),因此必须获取新令牌。为此,应使用刷新令牌。刷新令牌允许客户端请求新的访问令牌,而无需用户交互(离线访问)。

使用新的访问令牌直到它过期并且必须请求一个新的令牌。直到刷新令牌本身过期,但可以配置。


在implicit flow 中没有刷新令牌,但访问令牌确实会过期。所以你需要另一种方法来刷新令牌。为此,您可以使用类似 silent token renew 的实现。

有关术语,请阅读documentation。

请注意各种流程。这一切都取决于具体情况。有没有用户,是浏览器应用,有没有前通道,后通道,是否需要离线访问,客户端可以保密吗?在选择流程之前需要考虑的事项。

选择流程后,您需要为客户端配置允许的授权。如果只允许隐式授权类型,则使用客户端凭据的客户端无法访问资源。

IdentityServer 主要用于配置客户端和资源。查看示例以了解不同的流程及其配置方式。

【讨论】:

这是一个非常棒的回复。我不能再赏你两天,但我完全打算这样做。很努力!话虽如此,我有一些澄清的要求。 (马上就在下面。) 我已经成功地使客户的信用流动,所以它是落后的。但是,现在,我想更好地了解我的 SPA 将如何进行身份验证/授权。如果我理解正确,客户将不知道用户名/密码。但是,它们将如何在后端进行处理呢?如果我与 Postman 通话并在帖子正文中指定我的凭据(根据文档的要求),我在哪里可以捕获它们并评估我的用户数据库?这部分对我来说是黑魔法。我需要在我的客户中进行哪些更改才能实现它? (我已经看过所有的例子,但没有更聪明。) 这是最难的部分。因为它是一个需要模拟的流程。我在网上某处看过一个教程,但我没有完成它,因为它的步骤太多。我选择了简单的方法:定义一个令牌变量,在显示访问令牌的安全页面上登录,然后将令牌复制/粘贴到变量中。 客户端不知道凭据。它只能使用访问令牌。无论在 Postman 中如何获取访问令牌,客户端都会将其与授权标头中的请求一起发送:'Bearer '。对于自动登录检查this。它引用了一个包含 WebApi 和 IdentityServer4 快速入门的 example——使用用户名:alice 和密码:password。 在 IdentityServer 中有一个登录页面。那是用户登录和 UserManager 访问数据库的地方。成功登录后,用户将按照配置路由回客户端。对于访问令牌,可以使用端点。这些调用不会到达任何控制器。可以使用自省来验证访问令牌。但不是必需的。 JWT 是独立的,请阅读我的回答here 了解更多信息。【参考方案2】:

客户端只允许使用隐式流获取令牌来访问受 Identity Server 保护的资源:

AllowedGrantTypes = GrantTypes.Implicit,

但是您的客户正在使用Resource Owner Flow:

此授权类型适用于能够获得 资源所有者的凭据(用户名和密码,通常使用 交互式表格)。它还用于迁移现有客户端 使用直接身份验证方案,例如 HTTP Basic 或 Digest 通过将存储的凭据转换为 OAuth 进行身份验证 访问令牌。

如果您使用的是 SPA 应用程序,您应该使用隐式流来获取令牌,而不会将最终用户凭据暴露给第三方。

通常,您有三个应用程序:客户端应用程序、身份服务器(使用用户 db)和 api。使用隐式流时:

    客户端将用户重定向到身份服务器应用,身份服务器提供 UI 让用户输入他们的凭据。 用户输入凭据后,身份服务器将验证数据库/配置文件中的凭据。当然你也可以在身份服务器中配置外部登录。 验证凭据后,身份服务器将根据客户端 OpenID Connect 配置中的回调 url 将 ID 令牌和访问令牌(如果范围包括 API 资源)发回您的客户端应用程序。 客户端应用程序将验证和解码 ID 令牌和登录用户。您可以使用 SDK 或直接手动处理。 如果你得到了访问令牌,你可以将访问令牌保存在会话缓存中,它可以用来访问受保护的资源,直到它过期。

【讨论】:

是什么让你说我在使用资源所有者流程?我确实有 ...,AllowedGrantTypes = GrantTypes.Implicit,AllowAccessTokensViaBrowser = true,...。我错过了什么? 另外,我刚刚意识到 #1 的问题。如果是身份服务器提供 UI 以输入凭据,它如何与不同的 SPA 押韵(完全不同的 Web 应用程序,我无法控制去那里重定向?不同应用程序的样式可能会有所不同。GUI 不能支持他们,可以吗? @KonradViltersten ,在客户端你是 requesting a token using the password Grant Type ,你可以使用 fiddler 来跟踪请求,grant_type 应该是 password ,这就是你发生错误的原因。因为在身份服务器中你配置了应该允许客户端使用隐式流。 @KonradViltersten 。不同的客户端应该在身份服务器中注册不同的回调url,身份服务器验证凭据后,会根据回调url重定向回客户端。如果您跟踪令牌请求,您会发现回调 url 包含在查询字符串中:openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowSteps

以上是关于由于未授权客户端错误,无法获取令牌的主要内容,如果未能解决你的问题,请参考以下文章

无法在POSTMan上获取Google oAuth 2令牌

无法获取 lyft api 的访问令牌

刷新谷歌访问令牌时未授权_客户端

无法获取 google play 授权的刷新令牌

客户端未经授权使用此方法检索访问令牌,或未授权客户端使用任何请求的范围

如何从 BitBucket OAuth 获取令牌