使用JSON的WebAPI2.0 OWIN令牌请求
我已经在VisualStudio中创建了一个新的WebAPI解决方案,并且正在使用这些代码来尝试和理解正在发生的事情 我有一个测试API,它与一个授权控制器和另一个实现所有实际功能的控制器一起运行 除了/Token请求之外,控制器(API)都通过接收JSON并用JSON应答来工作。这必须是:使用JSON的WebAPI2.0 OWIN令牌请求,json,asp.net-web-api,asp.net-web-api2,owin,forms,Json,Asp.net Web Api,Asp.net Web Api2,Owin,Forms,我已经在VisualStudio中创建了一个新的WebAPI解决方案,并且正在使用这些代码来尝试和理解正在发生的事情 我有一个测试API,它与一个授权控制器和另一个实现所有实际功能的控制器一起运行 除了/Token请求之外,控制器(API)都通过接收JSON并用JSON应答来工作。这必须是: Content-Type: application/x-www-form-urlencoded 否则我只会得到一个错误 创建此端点的代码部分如下所示: OAuthOptions = new OAuthAu
Content-Type: application/x-www-form-urlencoded
否则我只会得到一个错误
创建此端点的代码部分如下所示:
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
};
这样调用会导致200个成功响应,其中包含一个承载令牌:
$("#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;
});
});
这样调用会得到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;
});
});
答复机构是:
{"error":"unsupported_grant_type"}
这里唯一的区别是用于传输请求的编码。
我所看到的每一个例子都使用表单编码来请求这个令牌
在/api/Account/ExternalLogin下的代码上放置断点,永远不会被命中
是否有理由只接受表单编码?如果不是,我如何更改控制器以接受JSON
或者,我只是做了一些愚蠢的事情吗?不需要
JSON.stringify(data)
直接传递数据。使用application/x-www-form-urlencoded
作为内容类型
的原因很简单:令牌请求需要这种内容类型
任何其他内容类型都将破坏与OAuth2兼容的客户端兼容性。我建议你不要改变这种标准行为
注意请注意:
postData("Token", data, "application/json", function (data)
{
//...
}
只因为您根本不发送JSON,所以工作正常!即使您将application/json
添加为Content-Type
头,您的请求主体也会序列化为表单键值对(AJAX调用中的jQuery默认对象序列化)
OAuthAuthorizationServerMiddleware的默认实现(更确切地说是内部使用的)从Microsoft.Owin.Security.OAuth
中,只需忽略内容类型
标题,并尝试将请求正文作为表单读取。OAuth2需要应用程序/x-www-form-urlencoded
令牌请求的内容类型
尽管如此,我还是想到了这个解决方法:
// GET api/Account/GetToken
[HttpPost]
[AllowAnonymous]
[Route("GetToken")]
public async Task<IHttpActionResult> GetToken(TokenRequest request)
{
var client = new HttpClient()
{
BaseAddress = new Uri(Request.RequestUri.GetLeftPart(UriPartial.Authority))
};
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", request.Username),
new KeyValuePair<string, string>("password", request.Password)
});
var result = await client.PostAsync("/token", content);
string resultContent = await result.Content.ReadAsStringAsync();
resultContent = resultContent.Replace(".issued", "issued").Replace(".expires", "expires");
TokenResponse tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(resultContent);
return Ok(tokenResponse);
}
它可以改进,但效果很好。一位客户的技术专家要求我们的/token端点可以在正文中同时使用“application/x-www-form-urlencoded”和“application/json”格式。
所以我不得不实现它,尽管它违反了规范
如果路径为“/api/token”,内容类型为“application/JSON”,则创建一个Owin中间件,将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;
}
}
公共密封类JSONBodyTourenCodedBodymiddleware:OwinMiddleware
{
公共JsonBodyToUrlEncodedBodyMiddleware(OWINNEXT)
:base(下一个)
{
}
公共重写异步任务调用(IOwinContext上下文)
{
if(string.Equals(context.Request.ContentType,“application/json”)
&&等于(context.Request.Method,“POST”,StringComparison.InvariantCultureIgnoreCase)
&&context.Request.Path==新路径字符串(“/avi/token/”)
{
尝试
{
等待替换OnBodyWithUrlEncodedBody(上下文);
等待下一步。调用(上下文);
}
捕获(例外)
{
context.Response.StatusCode=(int)HttpStatusCode.BadRequest;
Write(“无效的JSON格式”);
}
}
其他的
{
等待下一步。调用(上下文);
}
}
专用异步任务ReplaceJsonBodyWithUrlEncodedBody(IOwinContext上下文)
{
var requestParams=await GetFormCollectionFromJsonBody(上下文);
var urlEncodedParams=string.Join(“&”,requestParams.Select(kvp=>$”{kvp.Key}={kvp.Value}”);
var decryptedContent=newstringcontent(urlEncodedParams,Encoding.UTF8,“application/x-www-form-urlencoded”);
var requestStream=await decryptedContent.ReadAsStreamAsync();
context.Request.Body=requestStream;
}
私有静态异步任务GetFormCollectionFromJsonBody(IOwinContext上下文)
{
context.Request.Body.Position=0;
var jsonString=wait new StreamReader(context.Request.Body).ReadToEndAsync();
var requestParams=JsonConvert.DeserializeObject(jsonString);
返回请求参数;
}
}
无需JSON.stringify(数据)
感谢@gauravbhavsar现在可以工作了。如果你把它作为一个答案贴出来,我会认为它是正确的。我想这就提出了一个问题,为什么我需要JSON.stringify发布到其他端点的数据,而不是那个端点?我刚刚测试过,我确实需要。我会仔细研究一下,看看我是否能解决这个问题。谢谢你的全面回答。我已经验证了在不使用JSON.stringify()时POST数据不是JSON。我会按照你的建议去做,让它保持原样,这一个调用必须是form,其余的调用必须是JSON。谢谢你的链接。这对我帮助很大。有时阅读协议定义更有意义:)@Morvael Hi,你能解释一下你是如何验证POST数据不是JSON的吗?@psj01我通常使用任何浏览器的“网络”选项卡,我使用开发人员工具,并深入到请求有效负载,如果内容是“名称”=“值”对或JSON,应该很明显。除此之外,像Fiddler()这样的工具将允许您从基于非浏览器(或浏览器)的应用程序捕获流量。再次感谢您的帮助,但是我现在明白了为什么这样做,我觉得我不能将此标记为正确答案。
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;
}
}