Аутентификация AngularJS и OWIN на WebApi

Я реализовал аутентификацию на основе токенов OWIN на своем WebApi, я также включил CORS, вызвав app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll)

Я могу получить доступ к различным незащищенным частям моего приложения из веб-клиента angularjs. Я использовал этот http-перехватчик, когда я пытаюсь получить доступ к защищенному ресурсу, я получаю свой логин.

Теперь, чтобы войти в систему, я должен позвонить http://mywebapi/token с формой, закодированной в UserName Password и grant_type, см. мою подпись ниже (от chrome)

Request URL:http://mywebapi/token
Request Headers CAUTION: Provisional headers are shown.
Accept:application/json, text/plain, */*
cache:false
Content-Type:application/x-www-form-urlencoded
Origin:http://127.0.0.1:49408
Referer:http://127.0.0.1:49408/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Form Dataview sourceview URL encoded
grant_type:password
UserName:correctuser
Password:Password

Когда я использую почтальон для отправки этого запроса, он возвращается с ожидаемым accesstoken, однако, когда я пытаюсь использовать службу angular $http, он делает запрос OPTIONS (я вижу это в консоли инструментов Dev), но по какой-то причине я получаю это сообщение об ошибке

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:49408' is therefore not allowed access.

ПРИМЕЧАНИЕ. Это происходит только для запроса /token, который кодируется форму-url, для всех других json-запросов заголовок добавляется как ожидается. Кто-то может помочь, у меня заканчиваются идеи.

Спасибо

Ответ 1

Итак, я нашел ответ, но приготовлюсь к себе, потому что это странно! Я прочитал эту статью о проекте кода, которая привела меня к моему методу сервера авторизации Owin GrantResourceOwnerCredentials для проверки этого

context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

(Mine - это настраиваемый сервер авторизации (z), один из которых я отключил здесь)

Отвратительная вещь, которую я обнаружил, это то, что она уже была там!

Итак, я решил установить точку останова на этой строке и что вы знаете, эта строка терпит неудачу, потому что (... еще более шокирующее) "Access-Control-Allow-Origin" уже было в заголовках!

Итак, я прокомментировал это, и все это сработало! Но тогда оговорка, я понятия не имею, как заголовок попал, поэтому я понятия не имею, будет ли он там или нет в производстве, поэтому я поменял эту строку кода на это, чтобы проверить, а затем добавить его, если он еще не был там

var header = context.OwinContext.Response.Headers.SingleOrDefault(h => h.Key == "Access-Control-Allow-Origin");
            if (header.Equals(default(KeyValuePair<string, string[]>)))
            {
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
            }

Надеюсь, что мой труд любви спасет несколько душ от мучительного проклятия бесчисленных часов безрезультатного решения этой проблемы. Ура!

Ответ 2

Я прошел один и тот же процесс и потратил (потратил впустую?) столько же времени, сколько и большинство людей, занимающихся owin + web api.

Решением, которое работало для меня, было перемещение

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

перед всем остальным в трубе.

Вот какой код:

OwinStartup

[assembly: OwinStartup(typeof(MyApp.Web.Startup))]
namespace MyApp.Web
{
    using Owin;
    using Microsoft.Owin;

    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
        var config = new System.Web.Http.HttpConfiguration();
        ConfigureAuth(app, config);
        }
    }
}

Запуск для OAuth

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app, System.Web.Http.HttpConfiguration config)
        {
        // app.UseWelcomePage("/");
        // app.UseErrorPage();

        // Must be the first to be set otherwise it won't work.
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.CreatePerOwinContext<ApplicationDatabaseContext>(ApplicationDatabaseContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

        var OAuthOptions = new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new DaufAuthorizationServerProvider(),
            RefreshTokenProvider = new SimpleAuthorizationServerProvider(),
        };
        app.UseOAuthAuthorizationServer(OAuthOptions);

        app.UseWebApi(WebApiConfig.Register(config, logger));
        }
}

Web Api

public static class WebApiConfig
{
    public static HttpConfiguration Register(System.Web.Http.HttpConfiguration config, ILogger logger)
        {
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            // This will used the HTTP header: "Authorization"      Value: "Bearer 1234123412341234asdfasdfasdfasdf"
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            return (config);
        }
}

Ответ 3

Для тех, кто интересуется ответом и предыдущим ответом, он действительно сильно связан с заказом. Всякий раз, когда вы добавляете промежуточное ПО Owin, важно отметить: порядок регистрации является обязательным.

app.useCors(Microsoft.Owin.Cors.CorsOptions.AllowAll)

Наличие этого как первого в вашем файле auth, в основном регистрирует обработчик Cors, который должен произойти до достижения ваших OAuthServer и Web Api.

Перемещение его после OAuth делает обратное, поэтому необходимо добавить заголовок Access-Control-Allow-Origin в GrantResourceOwnerCredentials.

Чтобы ответить на другой вопрос, причина, по которой заголовок уже существует, если вы отправляете запрос CORS из браузера и указан CorsOptions.AllowAll, он добавляет его для вас, поэтому к моменту достижения конечной точки/токена сервер OAuth он уже добавил. (просто означает, что он был найден в запросе http, и вы разрешаете все происхождение).

Вы можете проверить поведение соответственно,

В Fiddler отправьте новый запрос в конечную точку Token с заголовком Origin, включенным с произвольным значением. Поместите контрольную точку на свой сервер OAuth в GrantResourceOwnerCredentials, а затем изучите context.Response.Headers, теперь он будет содержать исходное значение, в котором вы проходили. (Помните, браузер должен проверить это, скрипач будет счастлив весь день)

Если вы затем скажете CORS не использовать CorsOptions.AllowAll и установите AllowAnyOrigin на false, вы заметите, что источник, отправленный с Fiddler, больше не добавляется в заголовки ответов.

Браузер в свою очередь отклонит запрос CORS, потому что источник не был возвращен. Origin "" не найден в заголовке Access-Control-Allow-Origin.

СЕЙЧАС ДЛЯ ВАЖНОГО ДЕЙСТВИЯ:

Если вы установили CorsOptions.AllowAll, он делает именно то, что он говорит, что делает это, разрешает запросы CORS любому методу на любом промежуточном программном обеспечении, которое происходит после регистрации CORS в конвейере Owin, поэтому убедитесь, что это то, что вы намереваетесь сделать. IE: Если вы сначала зарегистрируете CORS, затем OAuth и веб-API, то все ваши методы Web API будут доступны через CORS, если вы явно не добавите атрибуты code\для предотвращения этого.

Если вы хотите ограничить методы, тогда реализуйте ICorsPolicyProvider, некоторые части из http://katanaproject.codeplex.com/(Microsoft.Owin.Cors)

      public class MyCorsPolicyProvider : ICorsPolicyProvider
        {
            public Task<CorsPolicy> GetCorsPolicyAsync(IOwinRequest request)
            {
                // Grant Nothing.
                var policy = new CorsPolicy
                {
                    AllowAnyHeader = false,
                    AllowAnyMethod = false,
                    AllowAnyOrigin = false,
                    SupportsCredentials = false
                };

                // Now we can get a bit clever: (Awesome, they requested the token endpoint. Setup OAuth options for that.
                if (OAuthOptions.TokenEndpointPath.HasValue && OAuthOptions.TokenEndpointPath == request.Path)
                {
                    // Hypothetical scenario, tokens can only be obtained using CORS when the Origin is http://localhost
                    policy.AllowAnyHeader = true;
                    policy.AllowAnyMethod = true;
                    policy.AllowAnyOrigin = false;
                    policy.SupportsCredentials = true;
                    policy.Origins.Add("http://localhost");

                return Task.FromResult(policy);
                }
                // No token?, must already have one.... so this must be a WebApi request then.
                // From here we could check where the request is going, do some other fun stuff etc... etc...
                // Alternatively, do nothing, set config.EnableCors() in WebApi, then apply the EnableCors() attribute on your methods to allow it through.
return null;            }
        }

Возврат null; сообщает Owin продолжить следующее промежуточное программное обеспечение и разрешить запрос через, но без политики, таким образом, NO CORS!, позволяя вам устанавливать соответствующие атрибуты CORS в WebAPI

Теперь действительно важный бит, НЕ добавляйте заголовок Access-Control-Allow-Origins к вашему ответу, если он не существует, если это действительно то, что вы намереваетесь в зависимости от вашего регистрационного заказа промежуточного программного обеспечения, оно откроет все двери для запросов CORS, если вы явно не блокируете их в другом месте или не удаляете заголовок и в основном вызывают много проблем при попытке использовать CORS с WebApi и хотите ограничить его.

Чтобы заблокировать их в другом месте, вы можете добавить CorsPolicyProvider (System.Web.Http) для WebApi, а затем установить переменную Context в Owin, которую вы можете прочитать, как только запрос попадет в WebApi.

    public class WebApiCorsPolicyProvider : System.Web.Http.Cors.ICorsPolicyProvider
    {
        public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var policy = new CorsPolicy
            {
                AllowAnyHeader = false,
                AllowAnyMethod = false,
                AllowAnyOrigin = false,
                SupportsCredentials = false
            };
// The benefit of being at this point in the pipeline is we have been authenticated\authorized so can check all our claims for CORS purposes too if needed and set errors etc...

            // In an Owin pipeline?
            var owinContext = request.GetOwinContext();

            if (owinContext != null)
            {
               // We have an owin pipeline, we can get owin parameters and other things here.
            }
            else
            {
                // Write your code here to determine the right CORS options. Non Owin pipeline variant.   
            }

            return Task.FromResult(policy);
        }
    }

И, наконец, еще одно преимущество распространения вниз поставщику политики WebSpi CORS заключается в том, что в этот момент будет выполнено авторизация, чтобы затем применить дополнительные фильтры Origin на этом этапе в поставщике политики CORS.

Ответ 4

По-моему, это связано с упорядочением ваших заявлений, хотя я не исследовал дальше. Я столкнулся с той же проблемой и попробовал все комбинации, и в конечном итоге после этого работал у меня.

        public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        ConfigureOAuth(app);
        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.UseWebApi(config);
    }

Я следил за Аутентификация на основе токена с использованием ASP.NET Web API 2, Owin и Identity

Ответ 5

Это еще одна версия кода для ответа Obi Onuorah

        string corsHeader = "Access-Control-Allow-Origin";
        if (!context.Response.Headers.ContainsKey(corsHeader))
        {
            context.Response.Headers.Add(corsHeader, new[] { "*" }); 
        }