【问题标题】:Authorization_code grant flow on Owin.Security.OAuth: returns invalid_grantOwin.Security.OAuth 上的 Authorization_code 授权流程:返回 invalid_grant
【发布时间】:2021-09-09 18:00:45
【问题描述】:

我正在尝试使用authorization_code 授权流程设置我的身份验证。我之前使用过grant_type=password,所以我知道这些东西应该如何工作。但是当使用grant_type=authorization_code 时,我无法让它返回invalid_grant以外的任何东西

这是我的设置:

app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
    AllowInsecureHttp = true,
    TokenEndpointPath = new PathString("/auth/token"),
    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
    Provider = new SampleAuthProvider()
});

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
    AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
    AuthenticationType = "Bearer"
});

SampleAuthProvider 是以下类:https://gist.github.com/anonymous/8a0079b705423b406c00

基本上,它只是记录每一步并对其进行验证。我尝试了请求:

POST http://localhost:12345/auth/token
grant_type=authorization_code&code=xxxxxx&client_id=xxxxx&redirect_uri=https://xxxx.com/
Content-Type: application/x-www-form-urlencoded

它正在经历:

  • OnMatchEndpoint
  • OnValidateClientAuthentication

仅此而已。我预计它会调用OnValidateTokenRequestOnGrantAuthorizationCodenext,但它没有。我不知道为什么。

请求中的xxxx 不是占位符,我试过这样。也许中间件会自行进行一些检查并因此拒绝请求?我尝试了redirect_urihttp 的变体,没有任何协议,没有斜杠...

它也适用于自定义grant_type。所以如果我太绝望了,我想我可以用它来模拟authorization_code,但我宁愿不必那样做。

TL;DR

当使用grant_type=authorization_code 时,我的OAuthAuthorizationServerProviderOnValidateClientAuthentication 之后返回{"error":"invalid_grant"}

  • 为什么会停在那里?
  • 我怎样才能让整个该死的东西发挥作用?

感谢您的帮助!


编辑

正如 RajeshKannan 所指出的,我在配置中犯了一个错误。我没有提供AuthorizationCodeProvider 实例。但是,这并没有完全解决问题,因为在我的例子中,代码不是由AuthorizationCodeProvider 发布的,我不能只是反序列化它。我接受了我正在工作的解决方法。

【问题讨论】:

    标签: asp.net oauth-2.0 owin owin.security


    【解决方案1】:

    这是我的工作。我对这个解决方案并不完全满意,但它很有效,应该可以帮助其他人解决他们的问题。


    所以,问题在于我没有设置AuthorizationCodeProvider 属性。当收到带有grant_type=authorization_code 的请求时,该代码必须由该代码提供者进行验证。该框架假定代码是由该代码提供者发布的,但这不是我的情况。我从另一台服务器获取它,并且必须将代码发送回它以进行验证。

    在标准情况下,您也是发布代码的人,RajeshKannan 提供的链接描述了您必须做的所有事情。

    这里是你必须设置属性的地方:

    app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString(Paths.TokenPath),
        Provider = new SampleAuthProvider(),
        AuthorizationCodeProvider = new MyAuthorizationCodeProvider ()
    }
    

    以及MyAuthorizationCodeProvider类的声明:

    internal class MyAuthorizationCodeProvider : AuthenticationTokenProvider
    {
        public override async Task ReceiveAsync(
            AuthenticationTokenReceiveContext context)
        {
            object form;
            // Definitely doesn't feel right
            context.OwinContext.Environment.TryGetValue(
                    "Microsoft.Owin.Form#collection", out form); 
            var redirectUris = (form as FormCollection).GetValues("redirect_uri");
            var clientIds = (form as FormCollection).GetValues("client_id");
            if (redirectUris != null && clientIds != null)
            {
                // Queries the external server to validate the token
                string username = await MySsoService.GetUserName(context.Token,
                                                                 redirectUris[0]);
                if (!string.IsNullOrEmpty(username))
                {
                    var identity = new ClaimsIdentity(new List<Claim>()
                    {
                        // I need the username in  GrantAuthorizationCode
                        new Claim(ClaimTypes.NameIdentifier, username) 
                    }, DefaultAuthenticationTypes.ExternalBearer);
    
                    var authProps = new AuthenticationProperties();
    
                    // Required. The request is rejected if it's not provided
                    authProps.Dictionary.Add("client_id", clientIds[0]); 
    
                    // Required, must be in the future
                    authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1); 
    
                    var ticket = new AuthenticationTicket(identity, authProps);
                    context.SetTicket(ticket);
                }
            }
        }
    }
    

    【讨论】:

    • 你好@scenario,你找到更好的方法了吗?我也有同样的问题
    • 抱歉,我停止了该项目的工作,因此我没有关于该主题的进一步更新。不过,该解决方案效果很好。 API 的设计考虑了其他用例。也许在未来的更新中,无需硬编码的密钥、演员表和 co 就可以实现。
    【解决方案2】:

    我有同样的错误。我缺少的东西:

    • 根据documentation指定OAuthAuthorizationServerOptions.AuthorizationCodeProvider
    • 向令牌端点发出请求时指定相同的 client_id 作为 GET 参数,就像收到 authorization_code 时所做的那样。
    • 覆盖OAuthAuthorizationServerProvider.ValidateClientAuthentication,并在此方法中调用context.TryGetFormCredentials。这会将属性 context.ClientId 设置为来自 client_id GET 参数的值。这个属性must be set,否则你会得到invalid_grant 错误。另外,请致电context.Validated()

    完成上述所有操作后,我终于可以在令牌端点将authorization_code 交换为access_token

    【讨论】:

    • 对我不起作用。我正在做这一切,但是当我对 OAuthAuthorizationServerProvider.ValidateClientAuthenticationIAuthenticationTokenProvider.ReceiveAsync 的实现被调用(按此顺序)时,我对 OAuthAuthorizationServerProvider.GrantAuthorizationCode 的实现永远不会被触发,我最终得到 400: invalid_grant。
    • 加 1,因为 client_id 建议很好并且确实需要。
    【解决方案3】:

    感谢方案,我的代码缺少以下两个必需值。在这里发布以防其他人发现它有用:

                // Required. The request is rejected if it's not provided
                authProps.Dictionary.Add("client_id", clientIds[0]); 
    
                // Required, must be in the future
                authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1); 
    

    【讨论】:

      【解决方案4】:

      确保您已配置授权服务器选项。 我认为您应该提供您的授权端点详细信息:

       AuthorizeEndpointPath = new PathString(Paths.AuthorizePath)
      

      在下面的链接中,将详细解释授权码授予,并列出了授权码授予生命周期中涉及的方法。

      Owin Oauth authorization server

      【讨论】:

      • 感谢您的回答让我走上了正轨。我编辑了问题以解释我必须更改的内容。但这只是感觉很奇怪。我是否错过了一种更简单的方法来获取查询参数并将它们传递给提供者?或者我不应该那样联系我的 SSO 服务器?
      【解决方案5】:

      @dgn 的回答或多或少对我有用。这只是对此的扩展。事实证明,您可以向ClaimsIdentity 构造函数提供您想要的任何字符串。以下内容同样有效,并且兼作详细的代码注释:

      var identity = new ClaimsIdentity(
          @"Katana - What a shitty framework/implementation.
          Unintuitive models and pipeline, pretty much have to do everything, and the docs explain nothing. 
          Like what can go in here? WTF knows but turns out as long as _something_ is in here, 
          there is a client_id key in your AuthenticationProperties with the same value as 
          what's set inside your implementation for OAuthAuthorizationServerProvider.ValidateClientAuthentication, and
          your AuthenticationProperties.ExpiresUtc is set to some time in the future, it works.
          Oh and you don't actually need to supply an implementation for OAuthAuthorizationServerProvider.GrantAuthorizationCode...
          but if you are using the resource owner grant type, you _do_ need to supply an implementation of 
          OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials. Hmm. Whatever.
          Katana and IdenetityServer - two frameworks that are absolute garbage. In the amount of time it took me to
          figure out all the observations in this paragraph, I could've written my own /token endpoint."
      );
      

      【讨论】:

      • 我已经多次陷入这个陷阱?它与那些框架无关,但是如果你想要一个经过身份验证的身份,你需要将一个身份验证类型传递给构造函数,这可以在文档中的备注:docs.microsoft.com/en-us/dotnet/api/…
      【解决方案6】:

      我用下面最简单的例子解决了这个问题,并想分享它。希望有人觉得它有帮助。

      --

      中间件似乎会检查 redirect_uri 的键是否存在于 AuthenticationProperties 的字典中,将其删除,一切正常(使用经过验证的上下文)。

      AuthorizationCodeProvider 的简化示例如下:

      public class AuthorizationCodeProvider:AuthenticationTokenProvider {
          public override void Create(AuthenticationTokenCreateContext context) {
              context.SetToken(context.SerializeTicket());
          }
      
          public override void Receive(AuthenticationTokenReceiveContext context) {
              context.DeserializeTicket(context.Token);
      
              context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
          }
      }
      

      不要忘记在覆盖的方法OAuthAuthorizationServerProvider.ValidateClientAuthentication 中验证上下文。同样,这是一个从模板项目的ApplicationOAuthProvider 类继承的简化示例:

      public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
          public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
              if(null!=context.RedirectUri) {
                  context.Validated(context.RedirectUri);
                  return Task.CompletedTask;
              }
      
              return base.ValidateClientRedirectUri(context);
          }
      
          public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
              if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
                  // Specify the actual expected client id and secret in your case
                  if(("expected-clientId"==clientId)&&("expected-clientSecret"==clientSecret)) {
      
                      context.Validated(); // <-
      
                      return Task.CompletedTask;
                  }
              }
      
              return base.ValidateClientAuthentication(context);
          }
      
          public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
          }
      }
      

      请注意,如果您使用特定的客户端 ID 调用 context.Validated,那么您必须将相同的 client_id 放入票证的属性中,您可以使用方法 AuthenticationTokenProvider.Receive 来做到这一点

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-19
        • 2019-09-26
        • 2020-11-25
        相关资源
        最近更新 更多