【问题标题】:Getting "error": "unsupported_grant_type" when trying to get a JWT by calling an OWIN OAuth secured Web Api via Postman尝试通过 Postman 调用 OWIN OAuth 安全 Web Api 来获取 JWT 时出现“错误”:“unsupported_grant_type”
【发布时间】:2022-03-03 04:36:37
【问题描述】:

我已经按照this article 实现了一个 OAuth 授权服务器。但是,当我使用 post man 获取令牌时,响应中出现错误:

“错误”:“unsupported_grant_type”

我在某处读到 Postman 中的数据需要使用 Content-type:application/x-www-form-urlencoded 发布。我已经在 Postman 中准备好所需的设置:

然而我的标题是这样的:

这是我的代码

public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
        return Task.FromResult<object>(null);
    }

    public override Task MatchEndpoint(OAuthMatchEndpointContext context)
    {
        if (context.OwinContext.Request.Method == "OPTIONS" && context.IsTokenEndpoint)
        {
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST" });
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "accept", "authorization", "content-type" });
            context.OwinContext.Response.StatusCode = 200;
            context.RequestCompleted();
            return Task.FromResult<object>(null);
        }
        return base.MatchEndpoint(context);       
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        string allowedOrigin = "*";

        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Content-Type" });

        Models.TheUser user = new Models.TheUser();
        user.UserName = context.UserName;
        user.FirstName = "Sample first name";
        user.LastName = "Dummy Last name";

        ClaimsIdentity identity = new ClaimsIdentity("JWT");

        identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        foreach (string claim in user.Claims)
        {
            identity.AddClaim(new Claim("Claim", claim));    
        }

        var ticket = new AuthenticationTicket(identity, null);
        context.Validated(ticket);
    }
}

public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
    private readonly string _issuer = string.Empty;

    public CustomJwtFormat(string issuer)
    {
        _issuer = issuer;
    }

    public string Protect(AuthenticationTicket data)
    {
        string audienceId = ConfigurationManager.AppSettings["AudienceId"];
        string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["AudienceSecret"];
        var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
        var signingKey = new HmacSigningCredentials(keyByteArray);
        var issued = data.Properties.IssuedUtc;
        var expires = data.Properties.ExpiresUtc;
        var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
        var handler = new JwtSecurityTokenHandler();
        var jwt = handler.WriteToken(token);
        return jwt;
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        throw new NotImplementedException();
    }
}

在上面的 CustomJWTFormat 类中,只有构造函数中的断点被命中。在 CustomOauth 类中,GrantResourceOwnerCredentials 方法中的断点永远不会被命中。其他人都这样做。

启动类:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);

        ConfigureOAuthTokenGeneration(app);
        ConfigureOAuthTokenConsumption(app);

        app.UseWebApi(config);
    }

    private void ConfigureOAuthTokenGeneration(IAppBuilder app)
    {
        var OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            //For Dev enviroment only (on production should be AllowInsecureHttp = false)
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/oauth/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new CustomOAuthProvider(),
            AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["Issuer"])
        };

        // OAuth 2.0 Bearer Access Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
    }

    private void ConfigureOAuthTokenConsumption(IAppBuilder app)
    {
        string issuer = ConfigurationManager.AppSettings["Issuer"]; 
        string audienceId = ConfigurationManager.AppSettings["AudienceId"];
        byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);

        // Api controllers with an [Authorize] attribute will be validated with JWT
        app.UseJwtBearerAuthentication(
            new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = new[] { audienceId },
                IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                {
                    new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                }
            });
    }
}

我需要在 web api 代码的其他地方设置Content-type:application/x-www-form-urlencoded 吗?有什么问题?请帮忙。

【问题讨论】:

  • 我不想传递用户名密码,我想使用 twitter 消费者密钥和消费者秘密等外部提供者进行验证,我该怎么做?
  • 感谢您,尽管您的问题和最终答案实际上并不是我想要的,但内联 sn-p 似乎已经为我解决了这个问题。我正在努力解决受客户端 ID/秘密保护的 OPTIONS 身份验证令牌点。你救了我!
  • 确保您的授权码没有过期。我正在使用邮递员来测试我的代码。该请求包含旧的授权码。

标签: c# asp.net-web-api oauth-2.0 owin


【解决方案1】:

回复有点晚了——但万一以后有人遇到问题……

从上面的屏幕截图 - 似乎您将 url 数据(用户名、密码、授权类型)添加到标题而不是正文元素。

点击body标签,然后选择“x-www-form-urlencoded”单选按钮,下面应该有一个key-value列表,可以输入请求数据

【讨论】:

  • 我怎样才能从角度服务调用中发出相同的请求?
  • 我犯了同样的错误。通过在当前版本的 Postman 中选择“x-www-form-urlencoded”,“Content-Type”键将自动添加到 Headers 选项卡中。其他参数需要在 Body 选项卡中添加。
  • 为什么它不能使用 body 的表单数据来代替 x-www-form-urlencoded?
【解决方案2】:

使用 Postman,选择 Body 选项卡并选择 raw 选项并输入以下内容:

grant_type=password&username=yourusername&password=yourpassword

【讨论】:

  • 对于包含p@ssword 等特殊字符的正文,是否需要将“@”替换为“%40”?
  • @GregDegruy 看起来只有密码必须是 url 编码的。用户名可以包含“@”,但我必须将密码中的 & 替换为 %26
【解决方案3】:
  1. 注意网址:localhost:55828/token(不是localhost:55828/API/token
  2. 记下请求数据。它不是 json 格式,它只是没有双引号的纯数据。 userName=xxx@gmail.com&amp;password=Test123$&amp;grant_type=password
  3. 注意内容类型。 Content-Type: 'application/x-www-form-urlencoded' (不是 Content-Type: 'application/json')
  4. 当您使用 JavaScript 发出 post 请求时,您可以使用以下方式:

    $http.post("localhost:55828/token", 
      "userName=" + encodeURIComponent(email) +
        "&password=" + encodeURIComponent(password) +
        "&grant_type=password",
      {headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}
    ).success(function (data) {//...
    

请看下面 Postman 的截图:

【讨论】:

  • 我正在发送与上述相同的请求,但仍然收到 invalid_grant。请提出解决方案。
  • 很好的答案!步骤有所帮助。
  • Postman 根据选择的正文自动选择内容类型的标题。输入文本内容时不能改成application/x-www-form-urlencoded
【解决方案4】:

如果你使用 AngularJS,你需要将 body 参数作为字符串传递:

    factory.getToken = function(person_username) {
    console.log('Getting DI Token');
    var url = diUrl + "/token";

    return $http({
        method: 'POST',
        url: url,
        data: 'grant_type=password&username=myuser@user.com&password=mypass',
        responseType:'json',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    });
};

【讨论】:

  • 也与角度 2 相关,我试图将数据变量作为对象传递,但出现错误,将数据作为字符串传递解决了它。
【解决方案5】:

尝试将其添加到您的有效负载中

grant_type=password&username=pippo&password=pluto

【讨论】:

    【解决方案6】:

    我也遇到了这个错误,原因最终是错误的调用 url。如果其他人碰巧混合了网址并收到此错误,我将把这个答案留在这里。我花了几个小时才意识到我的网址有误。

    我得到的错误(HTTP 代码 400):

    {
        "error": "unsupported_grant_type",
        "error_description": "grant type not supported"
    }
    

    我在打电话:

    https://MY_INSTANCE.lightning.force.com

    虽然正确的 URL 应该是:

    https://MY_INSTANCE.cs110.my.salesforce.com

    【讨论】:

      【解决方案7】:

      老问题,但对于angular 6,这需要在您使用HttpClient 时完成 我在这里公开公开令牌数据,但如果通过只读属性访问会很好。

      import { Injectable } from '@angular/core';
      import { HttpClient } from '@angular/common/http';
      import { Observable, of } from 'rxjs';
      import { delay, tap } from 'rxjs/operators';
      import { Router } from '@angular/router';
      
      
      @Injectable()
      export class AuthService {
          isLoggedIn: boolean = false;
          url = "token";
      
          tokenData = {};
          username = "";
          AccessToken = "";
      
          constructor(private http: HttpClient, private router: Router) { }
      
          login(username: string, password: string): Observable<object> {
              let model = "username=" + username + "&password=" + password + "&grant_type=" + "password";
      
              return this.http.post(this.url, model).pipe(
                  tap(
                      data => {
                          console.log('Log In succesful')
                          //console.log(response);
                          this.isLoggedIn = true;
                          this.tokenData = data;
                          this.username = data["username"];
                          this.AccessToken = data["access_token"];
                          console.log(this.tokenData);
                          return true;
      
                      },
                      error => {
                          console.log(error);
                          return false;
      
                      }
                  )
              );
          }
      }
      

      【讨论】:

        【解决方案8】:

        此“unsupported_grant_type”错误的另一个常见原因是将 API 调用为 GET 而不是 POST。

        【讨论】:

          【解决方案9】:

          使用grant_type={您的密码}

          【讨论】:

          • 请解释这是如何回答发布的问题的?
          猜你喜欢
          • 2021-05-29
          • 1970-01-01
          • 2021-09-22
          • 1970-01-01
          • 2015-10-08
          • 1970-01-01
          • 1970-01-01
          • 2020-04-11
          • 2011-01-19
          相关资源
          最近更新 更多