尝试通过 Postman 调用 OWIN OAuth 安全 Web Api 来获取 JWT 时出现“错误”:“unsupported_grant_type”

Posted

技术标签:

【中文标题】尝试通过 Postman 调用 OWIN OAuth 安全 Web Api 来获取 JWT 时出现“错误”:“unsupported_grant_type”【英文标题】:Getting "error": "unsupported_grant_type" when trying to get a JWT by calling an OWIN OAuth secured Web Api via Postman 【发布时间】: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 身份验证令牌点。你救了我! 确保您的授权码没有过期。我正在使用邮递员来测试我的代码。该请求包含旧的授权码。 【参考方案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】:
    注意网址:localhost:55828/token(不是localhost:55828/API/token) 记下请求数据。它不是 json 格式,它只是没有双引号的纯数据。 userName=xxx@gmail.com&amp;password=Test123$&amp;grant_type=password 注意内容类型。 Content-Type: 'application/x-www-form-urlencoded' (不是 Content-Type: 'application/json')

    当您使用 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=您的密码

【讨论】:

请解释这是如何回答发布的问题的?

以上是关于尝试通过 Postman 调用 OWIN OAuth 安全 Web Api 来获取 JWT 时出现“错误”:“unsupported_grant_type”的主要内容,如果未能解决你的问题,请参考以下文章

通过 Postman 调用 National Rail SOAP XML API

Api 调用适用于从 Postman 获取的令牌,但不适用于通过 nodejs 应用程序获取的令牌

修改 OWIN OAuth 中间件以使用 JWT 不记名令牌

通过 OWIN 启动重定向控制器

如何使用 Postman 客户端调用 Twitter API

Owin 身份令牌认证令牌端点以 404 响应