使用JSON的WebAPI2.0 OWIN令牌请求

Mor*_*ael 14 forms json asp.net-web-api owin asp.net-web-api2

我在visual studio中创建了一个新的WebAPI解决方案,并且正在使用代码来尝试了解最新情况.

我有一个测试API,它全部启动并运行授权控制器和另一个实现所有实际功能的控制器.

控制器(API)都通过接收JSON并使用JSON进行回复来工作,但/ Token请求除外.这必须是:

Content-Type: application/x-www-form-urlencoded
Run Code Online (Sandbox Code Playgroud)

否则我只是得到一个错误.

创建此端点的代码部分似乎是这样的:

OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // In production mode set AllowInsecureHttp = false
    AllowInsecureHttp = false
};
Run Code Online (Sandbox Code Playgroud)

像这样调用它会导致200 Success响应,带有Bearer令牌:

$("#token_button").click(function ()
{
    var username = $("#token_email").val();
    var password = $("#token_password").val();

    postData("Token", "grant_type=password&username=" + username + "&password=" + password, "application/x-www-form-urlencoded", function (data)
    {
        user = data;
        $("#feedback_display").html(user.access_token);
    }, function ()
    {
        user = null;
    });
});
Run Code Online (Sandbox Code Playgroud)

像这样调用它会导致400响应:

$("#token_button").click(function ()
{
    var username = $("#token_email").val();
    var password = $("#token_password").val();

    var data = {
        "grant_type": "password",
        "username": username,
        "password": password
    }

    postData("Token", JSON.stringify(data), "application/json", function (data)
    {
        user = data;
        $("#feedback_display").html(user.access_token);
    }, function ()
    {
        user = null;
    });
});
Run Code Online (Sandbox Code Playgroud)

回应机构是:

{"error":"unsupported_grant_type"}
Run Code Online (Sandbox Code Playgroud)

这里唯一的区别是用于传输请求的编码.我看的所有示例都使用表单编码来请求此令牌.

在/ api/Account/ExternalLogin下的代码上放置一个断点,永远不会被击中.

这是否只有接受表单编码的原因?如果不是,我怎么能改变控制器接受JSON?

或者我刚做了一些蠢事?

Fed*_*uma 14

使用application/x-www-form-urlencodedas 的原因Content-Type很简单:OAuth2规范(RFC 6749)要求令牌请求使用此内容类型.

任何其他内容类型都将破坏与OAuth2兼容的客户端兼容性.我建议你不要改变这个标准行为.

注意
请注意:

postData("Token", data, "application/json", function (data)
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

只是因为你根本没有发送JSON!即使您添加application/jsonContent-Type标头,您的请求主体也会被序列化为表单键值对(AJAX调用中的jQuery默认对象序列化).

默认的实现OAuthAuthorizationServerMiddleware(更确切地说是内部使用的OAuthAuthorizationServerHandler)Microsoft.Owin.Security.OAuth只是忽略Content-Type标题并尝试将请求主体作为一个表单来读取.


tse*_*sky 7

客户的技术专家要求我们的 /token 端点可以在正文中使用“application/x-www-form-urlencoded”和“application/json”格式。所以我必须实现它,尽管它违反了规范。

创建一个 Owin 中间件,如果路径为“/api/token”且内容类型为“application/json”,则将 JSON 正文转换为 Url 编码的正文。不要忘记在 Startup.cs 中注册它。

public sealed class JsonBodyToUrlEncodedBodyMiddleware : OwinMiddleware
    {
        public JsonBodyToUrlEncodedBodyMiddleware(OwinMiddleware next)
            : base(next)
        {
        }

        public override async Task Invoke(IOwinContext context)
        {
            if (string.Equals(context.Request.ContentType, "application/json")
                && string.Equals(context.Request.Method, "POST", StringComparison.InvariantCultureIgnoreCase)
                && context.Request.Path == new PathString("/avi/token/"))
            {
                try
                {
                    await ReplaceJsonBodyWithUrlEncodedBody(context);
                    await Next.Invoke(context);
                }
                catch (Exception)
                {
                    context.Response.StatusCode = (int) HttpStatusCode.BadRequest;
                    context.Response.Write("Invalid JSON format.");
                }
            }
            else
            {
                await Next.Invoke(context);
            }
        }

        private async Task ReplaceJsonBodyWithUrlEncodedBody(IOwinContext context)
        {
            var requestParams = await GetFormCollectionFromJsonBody(context);
            var urlEncodedParams = string.Join("&", requestParams.Select(kvp => $"{kvp.Key}={kvp.Value}"));
            var decryptedContent = new StringContent(urlEncodedParams, Encoding.UTF8, "application/x-www-form-urlencoded");
            var requestStream = await decryptedContent.ReadAsStreamAsync();
            context.Request.Body = requestStream;
        }

        private static async Task<Dictionary<string, string>> GetFormCollectionFromJsonBody(IOwinContext context)
        {
            context.Request.Body.Position = 0;
            var jsonString = await new StreamReader(context.Request.Body).ReadToEndAsync();
            var requestParams = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonString);
            return requestParams;
        }
    }
Run Code Online (Sandbox Code Playgroud)