当尝试通过Postman调用一个OWIN OAuth安全的Web API以获取JWT时出现“error”:“unsupported_grant_type”的错误。

89

我按照这篇文章实现了OAuth授权服务器。但是,当我使用Postman获取令牌时,响应中出现错误:

"error": "unsupported_grant_type"

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

enter image description here

但我的头文件却像这样:

enter image description here

以下是我的代码:

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消费者密钥和消费者秘密)进行验证,我该怎么做? - Neo
谢谢你,虽然你的问题和最终答案并不是我正在寻找的,但一个内联代码片段似乎已经为我解决了问题。我一直在努力处理由客户端ID/密钥保护的OPTIONS授权令牌点。你救了我! - The Senator
请确保您的授权码没有过期。我正在使用Postman测试我的代码。该请求包含旧的授权码。 - Paramvir Singh Karwal
10个回答

180

回应有点晚,但如果将来有人遇到这个问题...

从上面的截图中可以看出,您是将url数据(用户名、密码、授权类型)添加到头部而不是正文元素中。

点击正文选项卡,然后选择“x-www-form-urlencoded”单选按钮,下面应该有一个键值列表,您可以在其中输入请求数据。


3
如何使Angular服务调用的请求相同? - Parshwa Shah
我犯了同样的错误。在当前版本的Postman中选择“x-www-form-urlencoded”,键“Content-Type”将自动添加到标头选项卡中。其他参数应添加到正文选项卡中。 - Alielson Piffer
为什么使用表单数据作为x-www-form-urlencoded的替代品时它不起作用? - Bambam Deo

70

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

grant_type=password&username=yourusername&password=yourpassword

对于包含特殊字符的正文,比如 p@ssword,您需要将 "@" 替换为 "%40" 吗? - greg
1
@GregDegruy 看起来只有密码需要进行 URL 编码。用户名可以包含 '@',但我必须将密码中的 '&' 替换为 %26。 - Peter

25
  1. 注意URL: localhost:55828/token (而不是localhost:55828/API/token)
  2. 注意请求数据。它不是JSON格式,只是没有双引号的纯文本数据。 userName=xxx@gmail.com&password=Test123$&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中的截图:

Postman请求

Postman请求头


3
我正在发送与上述描述相同的请求,仍然收到“invalid_grant”错误。请建议解决方案。 - sadhana
1
很棒的答案!步骤很有帮助。 - OverMars
Postman根据所选的正文自动选择Content-Type标头。当输入文本内容时,我们无法将其更改为application/x-www-form-urlencoded。 - Rishabh Rusia

6
如果您正在使用AngularJS,您需要将请求体参数传递为字符串:
    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
同时也适用于Angular 2,我尝试将数据变量作为对象传递时出现了错误,将数据作为字符串传递解决了问题。 - Jonathan Applebaum

4

尝试在您的有效负载中添加此内容

grant_type=password&username=pippo&password=pluto

2
我也曾遇到这个错误,原因是调用的url错误。我在这里留下这个答案,以防其他人混淆url并出现此错误。我花了好几个小时才意识到我的URL是错误的。
我收到的错误(HTTP代码400):
{
    "error": "unsupported_grant_type",
    "error_description": "grant type not supported"
}

我原本在访问:

https://MY_INSTANCE.lightning.force.com

但正确的网址应该是:

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

“Original Answer”翻译成中文为“最初的回答”。


1

虽然这是一个老问题,但对于使用HttpClientangular 6来说,需要执行此操作。我在此公开了令牌数据,但最好通过只读属性访问。

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;

                }
            )
        );
    }
}

1
另一个常见的导致“unsupported_grant_type”错误的原因是将API调用方式误写为GET而非POST。

0
在Angular 13中,我做了以下操作;
let headers = new HttpHeaders({
  'Content-Type': 'application/x-www-form-urlencoded'
})
return this.http.post(this.url + 'token', 'grant_type=password&username='+form.username+'&password='+form.password, { headers: headers})

-4

使用 grant_type={您的密码} 在此输入图像描述


请解释一下这个回答如何回答了所发布的问题? - user230910

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接