Web API возвращает токен OAuth в виде XML

Используя шаблон проекта Visual Studio 2013 Web API по умолчанию с отдельными учетными записями пользователей и проводя публикацию в конечной точке /token с заголовком Accept application/xml, сервер по-прежнему возвращает ответ в формате JSON:

{"access_token":"...","token_type":"bearer","expires_in":1209599}

Есть ли способ вернуть токен в виде XML?

5 ответов

В соответствии с RFC6749 формат ответа должен быть JSON, и Microsoft реализовала его соответствующим образом. Я узнал, что форматирование JSON реализовано в Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerHandler внутренний класс без средств расширения.

Я также столкнулся с необходимостью иметь ответ токена в XML. Лучшее решение, которое я придумал, состояло в том, чтобы реализовать HttpModule, конвертирующий JSON в XML, когда указано в заголовке Accept.

public class OAuthTokenXmlResponseHttpModule : IHttpModule
{
    private static readonly string FilterKey = typeof(OAuthTokenXmlResponseHttpModule).Name + typeof(MemoryStreamFilter).Name;

    public void Init(HttpApplication application)
    {
        application.BeginRequest += ApplicationOnBeginRequest;
        application.EndRequest += ApplicationOnEndRequest;
    }

    private static void ApplicationOnBeginRequest(object sender, EventArgs eventArgs)
    {
        var application = (HttpApplication)sender;

        if (ShouldConvertToXml(application.Context.Request) == false) return;

        var filter = new MemoryStreamFilter(application.Response.Filter);
        application.Response.Filter = filter;
        application.Context.Items[FilterKey] = filter;
    }

    private static bool ShouldConvertToXml(HttpRequest request)
    {
        var isTokenPath = string.Equals("/token", request.Path, StringComparison.InvariantCultureIgnoreCase);
        var header = request.Headers["Accept"];

        return isTokenPath && (header == "text/xml" || header == "application/xml");
    }

    private static void ApplicationOnEndRequest(object sender, EventArgs eventArgs)
    {
        var context = ((HttpApplication) sender).Context;

        var filter = context.Items[FilterKey] as MemoryStreamFilter;
        if (filter == null) return;

        var jsonResponse = filter.ToString();
        var xDocument = JsonConvert.DeserializeXNode(jsonResponse, "oauth");
        var xmlResponse = xDocument.ToString(SaveOptions.DisableFormatting);

        WriteResponse(context.Response, xmlResponse);
    }

    private static void WriteResponse(HttpResponse response, string xmlResponse)
    {
        response.Clear();
        response.ContentType = "application/xml;charset=UTF-8";
        response.Write(xmlResponse);
    }

    public void Dispose()
    {
    }
}

public class MemoryStreamFilter : Stream
{
    private readonly Stream _stream;
    private readonly MemoryStream _memoryStream = new MemoryStream();

    public MemoryStreamFilter(Stream stream)
    {
        _stream = stream;
    }

    public override void Flush()
    {
        _stream.Flush();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _stream.Read(buffer, offset, count);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _memoryStream.Write(buffer, offset, count);
        _stream.Write(buffer, offset, count);
    }

    public override string ToString()
    {
        return Encoding.UTF8.GetString(_memoryStream.ToArray());
    }

    #region Rest of the overrides
    public override bool CanRead
    {
        get { throw new NotImplementedException(); }
    }

    public override bool CanSeek
    {
        get { throw new NotImplementedException(); }
    }

    public override bool CanWrite
    {
        get { throw new NotImplementedException(); }
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override long Length
    {
        get { throw new NotImplementedException(); }
    }

    public override long Position
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }
    #endregion
}

Хорошо, мне было так весело пытаться выяснить это с помощью OWIN. Я думал, что поделюсь своим решением с сообществом, я позаимствовал некоторые идеи из других постов /questions/36920321/kak-mozhno-bezopasno-perehvatit-potok-otvetov-v-polzovatelskom-promezhutochnom-programmnom-obespechenii-owin/36920324#36920324 и https://stackru.com/a/29105880/1148288 вместе с понятиями, которые Алексей описывает в своем посте. Ничего особенного в реализации не было, но у меня было требование, чтобы мой STS возвращал ответ в формате XML, я хотел придерживаться парадигмы соблюдения заголовка Accept, поэтому моя конечная точка проверит это, чтобы определить, нужно ли запускать своп XML или нет. Это то, что я сейчас использую:

private void ConfigureXMLResponseSwap(IAppBuilder app)
{
    app.Use(async (context, next) =>
    {
        if (context.Request != null && 
            context.Request.Headers != null &&
            context.Request.Headers.ContainsKey("Accept") &&
            context.Request.Headers.Get("Accept").Contains("xml"))
        {
            //Set a reference to the original body stream
            using (var stream = context.Response.Body)
            {
                //New up and set the response body as a memory stream which implements the ability to read and set length
                using (var buffer = new MemoryStream())
                {
                    context.Response.Body = buffer;

                    //Allow other middlewares to process
                    await next.Invoke();

                    //On the way out, reset the buffer and read the response body into a string
                    buffer.Seek(0, SeekOrigin.Begin);

                    using (var reader = new StreamReader(buffer))
                    {
                        string responsebody = await reader.ReadToEndAsync();

                        //Using our responsebody string, parse out the XML and add a declaration
                        var xmlVersion = JsonConvert.DeserializeXNode(responsebody, "oauth");
                        xmlVersion.Declaration = new XDeclaration("1.0", "UTF-8", "yes");

                        //Convert the XML to a byte array
                        var bytes = Encoding.UTF8.GetBytes(xmlVersion.Declaration + xmlVersion.ToString());

                        //Clear the buffer bits and write out our new byte array
                        buffer.SetLength(0);
                        buffer.Write(bytes, 0, bytes.Length);
                        buffer.Seek(0, SeekOrigin.Begin);

                        //Set the content length to the new buffer length and the type to an xml type
                        context.Response.ContentLength = buffer.Length;
                        context.Response.ContentType = "application/xml;charset=UTF-8";

                        //Copy our memory stream buffer to the output stream for the client application
                        await buffer.CopyToAsync(stream);
                    }
                }
            }
        }
        else
            await next.Invoke();
    });
}

Конечно, вы можете подключить это во время запуска конфигурации следующим образом:

public void Configuration(IAppBuilder app)
{
    HttpConfiguration httpConfig = new HttpConfiguration();
    //Highly recommend this is first...
    ConfigureXMLResponseSwap(app);
    ...more config stuff...
}

Надеюсь, что это поможет любым другим потерянным душам, которые найдут там путь к этому посту, стремясь сделать что-то подобное!

Посмотрите здесь, я надеюсь, что это может помочь, как настроить REST-сервис Web API, чтобы он всегда возвращал XML, а не JSON

Не могли бы вы повторить, выполнив следующие действия:

в WebApiConfig.Register(), уточнить

    config.Formatters.XmlFormatter.UseXmlSerializer = true;

    var supportedMediaTypes = config.Formatters.XmlFormatter.SupportedMediaTypes;

    if (supportedMediaTypes.Any(it => it.MediaType.IndexOf("application/xml", StringComparison.InvariantCultureIgnoreCase) >= 0) ==false)
    {
        supportedMediaTypes.Insert(0,new MediaTypeHeaderValue("application/xml"));

    }

Обычно я просто удаляю XmlFormatter.

// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);

Добавьте строку выше в свой класс WebApiConfig...

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

        // Web API routes
        config.MapHttpAttributeRoutes();

        // Remove the XML formatter
        config.Formatters.Remove(config.Formatters.XmlFormatter);

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}
Другие вопросы по тегам