【发布时间】:2019-01-16 15:28:25
【问题描述】:
我将使用 AuthorisationManager owin 中间件作为 Web API 来处理基于令牌的安全性。
我的问题是响应正文中的各种错误有各种不同的格式。
在我的 api 中,我通常用结构发回错误
{"code": "error code", "message": "error message"}
但是来自安全性的一些错误可能会使用
{"error": "error code", "error_description": "error message"}
或者有时只是
{"error": "error mesage"}
我想将这些统一起来,使其具有我在其他地方使用的相同结构,即
{"code": "error code", "message": "error message"}
我看过很多关于替换响应正文的帖子。
我首先尝试了this method,即使用DelegatingHandler。这在大多数情况下都有效,但它没有捕捉到来自我的OAuthAuthorizationServerProvider 的我的授权失败错误消息
接下来我尝试使用中间件方法作为shown here。
这是我的完整解释..
public override async Task Invoke(IOwinContext context)
{
try
{
// hold a reference to what will be the outbound/processed response stream object
var stream = context.Response.Body;
// create a stream that will be sent to the response stream before processing
using (var buffer = new MemoryStream())
{
// set the response stream to the buffer to hold the unaltered response
context.Response.Body = buffer;
// allow other middleware to respond
await this.Next.Invoke(context);
// Error codes start at 400. If we have no errors, no more to d0.
if (context.Response.StatusCode < 400) // <---- *** COMMENT1 ***
return;
// we have the unaltered response, go to start
buffer.Seek(0, SeekOrigin.Begin);
// read the stream
var reader = new StreamReader(buffer);
string responseBody = reader.ReadToEnd();
// If no response body, nothing to do
if (string.IsNullOrEmpty(responseBody))
return;
// If we have the correct error fields names, no more to do
JObject responseBodyJson = JObject.Parse(responseBody);
if (responseBodyJson.ContainsKey("code") && responseBodyJson.ContainsKey("message"))
return;
// Now we will look for the known error formats that we want to replace...
byte[] byteArray = null;
// The first one from the security module, errors come back as {error, error_description}.
// The contents are what we set (so are correct), we just want the fields names to be the standard {code, message}
var securityErrorDescription = responseBodyJson.GetValue("error_description");
var securityErrorCode = responseBodyJson.GetValue("error");
if (securityErrorDescription != null && securityErrorCode != null)
byteArray = CreateErrorObject(securityErrorCode.ToString(), securityErrorDescription.ToString());
// The next horrible format, is when a refresh token is just sends back an object with 'error'.
var refreshTokenError = responseBodyJson.GetValue("error");
if (refreshTokenError != null)
{
// We will give this our own error code
var error = m_resourceProvider.GetRefreshTokenAuthorisationError(refreshTokenError.ToString());
byteArray = CreateErrorObject(error.Item2, error.Item3);
}
else
{
byteArray = Encoding.ASCII.GetBytes(responseBody);
}
// Now replace the response (body) with our now contents
// <---- *** COMMENT2 ***
context.Response.ContentType = "application / json";
context.Response.ContentLength = byteArray.Length;
buffer.SetLength(0);
buffer.Write(byteArray, 0, byteArray.Length);
buffer.Seek(0, SeekOrigin.Begin);
buffer.CopyTo(stream);
}
}
catch (Exception ex)
{
m_logger.WriteError($"ResponseFormattingMiddleware {ex}");
context.Response.StatusCode = 500;
throw;
}
}
private byte[] CreateErrorObject(string code, string message)
{
JObject newMessage = new JObject();
newMessage["code"] = code;
newMessage["message"] = message;
return Encoding.ASCII.GetBytes(newMessage.ToString());
}
所以这基本上似乎有效,并且可以捕获所有响应,这很好。
但是,我希望做的是,当没有错误时(或者错误已经采用正确的格式),只需传递响应而不做任何事情。
我主要考虑我的一些 GET,其中数据可能很大,我希望避免进行额外的复制。在上面的代码中,我已经标记了*** COMMENT1 ***,我有一个提前返回来尽量避免这种情况,即行...
// Error codes start at 400. If we have no errors, no more to d0.
if (context.Response.StatusCode < 400)
return;
问题是,当我这样做时,我根本没有返回任何正文,即所有 GET 调用都没有数据,等等。
当我们不想做任何修改时,有没有办法避免这种额外的复制(即在*** COMMENT2 *** 行)?
提前感谢您的任何建议。
【问题讨论】:
-
你这里不是清空回复
context.Response.Body = buffer;。 -
为了更清楚:您正在实例化一个新的空内存流
using (var buffer = new MemoryStream())。所以即使这条线也没有多大意义,因为那时它仍然是空的buffer.Seek(0, SeekOrigin.Begin); -
为此,我基本上复制了示例代码,而且我必须承认,我发现那里发生的事情有点难以理解;对我来说,它确实看起来有点老套。
delegatingHandler似乎是“更正确”的方法,但它似乎并没有抓住所有的回应。对于我认为有人想做的常见任务(即有一个常见的错误响应格式),我感到非常沮丧。
标签: asp.net asp.net-web-api owin