问题
当我们使用app.OAuthBearerAuthenticationExtensions时,调用下一条链:
public static class OAuthBearerAuthenticationExtensions
{
public static IAppBuilder UseOAuthBearerAuthentication(this IAppBuilder app, OAuthBearerAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof (app));
app.Use((object) typeof (OAuthBearerAuthenticationMiddleware), (object) app, (object) options);
app.UseStageMarker(PipelineStage.Authenticate);
return app;
}
}
然后OAuthAuthorizationServerMiddleware 类型的对象使用内部类OAuthAuthorizationServerHandler,其中使用JsonTextWriter:
using (var jsonTextWriter = new JsonTextWriter((TextWriter) new StreamWriter((Stream) memory)))
{
jsonTextWriter.WriteStartObject();
jsonTextWriter.WritePropertyName("access_token");
jsonTextWriter.WriteValue(accessToken);
jsonTextWriter.WritePropertyName("token_type");
jsonTextWriter.WriteValue("bearer");
// and so on
this.Response.ContentLength = new long?((long) body.Length);
await this.Response.WriteAsync(body, this.Request.CallCancelled);
}
这里有两个限制:
*) JsonTextWriter 是纯类,不能配置,只是把字符串写成StringBuilder,所以Json.Settings = new MySettings() 不能应用。 JsontTextWriter 也不支持复杂对象。数组只能写为jsonTextWriter.WriteStartArray() 和jsonTextWriter.WriteEndArray(),但在OAuthAuthorizationServerHandler 中会被忽略。
*) 有些类是内部的,不能被覆盖或继承。
微软开发者似乎没有预见到这个问题,只是将自定义属性限制为IDictionary<string, string>。
解决方案 1
而不是app.UseOAuthBearerAuthentication(...) 应用您自己的代码
app.Use<MyOAuthBearerAuthenticationMiddleware>(options);
app.UseStageMarker(PipelineStage.Authenticate);
您可以从OAuthBearerAuthenticationMiddleware 派生一个类并用于您自己的目的。
解决方案 2
覆盖令牌端点响应。这是一件棘手的事情。
1) 创建一个自定义中间件,该中间件将包装其他调用并覆盖 Body 响应流。
class AuthenticationPermissionsMiddleware : OwinMiddleware
{
public AuthenticationPermissionsMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
if (!context.Request.Path.Equals("/Token")
{
await Next.Invoke(context);
return;
}
using (var tokenBodyStream = new MemoryStream())
{
// save initial OWIN stream
var initialOwinBodyStream = context.Response.Body;
// create new memory stream
context.Response.Body = tokenBodyStream;
// other middlewares will will update our tokenBodyStream
await Next.Invoke(context);
var tokenResponseBody = GetBodyFromStream(context.Response);
var obj = JsonConvert.DeserializeObject(tokenResponseBody);
var jObject = JObject.FromObject(obj);
// add your custom array or any other object
var scopes = new Scope[];
jObject.Add("scopes", JToken.FromObject(scopes));
var bytes = Encoding.UTF8.GetBytes(jObject.ToString());
context.Response.Body.Seek(0, SeekOrigin.Begin);
await tokenBodyStream.WriteAsync(bytes, 0, bytes.Length);
context.Response.ContentLength = data.LongLength;
tokenBodyStream.Seek(0, SeekOrigin.Begin);
// get back result to the OWIN stream
await context.Response.Body.CopyToAsync(initialOwinBodyStream);
}
}
}
private string GetBodyFromStream(IOwinResponse response)
{
using (var memoryStream = new MemoryStream())
{
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.CopyTo(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(memoryStream))
{
return reader.ReadToEnd();
}
}
}
}
2) 在认证启动方式中使用UseOAuthBearerTokens之前的新中间件。
app.Use<AuthenticationPermissionsMiddleware>();
app.UseOAuthBearerTokens(options);