При включении Bearer в запрос я получаю ошибку CORS, IdentityServer4, Api, angular

У меня возникли проблемы с получением данных после входа в систему.

Я использую IdentityServer4 с WebApi. Все работает до тех пор, пока я не отправлю авторизацию. Я использую перехватчик авторизации от Дэмиена Бода.

Когда я отправляю предъявителя, я получаю это обратно:

ПОЛУЧИТЬ https://localhost:44373/api/pages/10 10:1 XMLHttpRequest не может загрузить https://localhost:44373/api/pages/10. В запрошенном ресурсе отсутствует заголовок «Access-Control-Allow-Origin». Таким образом, доступ к источнику 'http://localhost:3000' запрещен. В ответе был код состояния HTTP 500.

Это работает до тех пор, пока я не добавляю заголовок авторизации в http-запрос.

Я использую образец IdentityServer, найденный здесь: https://github.com/IdentityServer/IdentityServer4.Samples/tree/dev/MVC%20and%20API/src

И службу безопасности, и перехватчик авторизации можно найти здесь: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow

Итак, проблема в том, что после того, как я вошел в систему и попытался получить данные из API, я получаю указанную выше ошибку.

API — Startup.cs

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        if (env.IsEnvironment("Development"))
        {
            // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
            builder.AddApplicationInsightsSettings(developerMode: true);
        }

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors();

        //services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
        //                                                            .AllowAnyMethod()
        //                                                             .AllowAnyHeader()));

        //var policy = new Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy();

        //policy.Headers.Add("*");
        //policy.Methods.Add("*");
        //policy.Origins.Add("*");
        //policy.SupportsCredentials = true;

        //services.AddCors(x => x.AddPolicy("AllowAll", policy));

        services.AddEntityFramework()
            .AddEntityFrameworkSqlServer()
            .AddDbContext<AsklepiosContext>(options =>               
                options.UseSqlServer(@"Server = .\SQLEXPRESS; Trusted_Connection = true; Database = AsklepiosTrunk; MultipleActiveResultSets = true;"));

        // Add framework services.
        services.AddApplicationInsightsTelemetry(Configuration);

        var guestPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .RequireClaim("scope", "api1")
            .Build();

        services.AddMvc(options =>
        {
            options.Filters.Add(new AuthorizeFilter(guestPolicy));
        });

        //DI
        // some services here .. 


    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseStaticFiles();

        app.UseApplicationInsightsRequestTelemetry();

        app.UseApplicationInsightsExceptionTelemetry();

        //app.UseCors("AllowAll");

        //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();

        //var jwtBearerOptions = new JwtBearerOptions()
        //{
        //    Authority = "https://localhost:44301",
        //    Audience = "https://localhost:44301/resources",
        //    AutomaticAuthenticate = true,

        //    // required if you want to return a 403 and not a 401 for forbidden responses
        //    AutomaticChallenge = true
        //};

        //app.UseJwtBearerAuthentication(jwtBearerOptions);

        //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();

        //var jwtBearerOptions = new JwtBearerOptions()
        //{
        //    Authority = "https://localhost:44302",
        //    Audience = "https://localhost:44302/resources",
        //    AutomaticAuthenticate = true,

        //    // required if you want to return a 403 and not a 401 for forbidden responses
        //    AutomaticChallenge = true,
        //};

        //app.UseJwtBearerAuthentication(jwtBearerOptions);

        //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

        app.UseCors("AllowAll");

        app.UseCors(policy =>
        {
            policy.WithOrigins(
                "http://localhost:3000");

            policy.AllowAnyHeader();
            policy.AllowAnyMethod();
        });


        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

        app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        {
            Authority = "https://localhost:44301",
            RequireHttpsMetadata = false,

            ScopeName = "api1",
            AutomaticAuthenticate = true
        });

        //app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        //{
        //    Authority = "https://localhost:44301",
        //    RequireHttpsMetadata = false,

        //    ScopeName = "dataEventRecords",
        //    AutomaticAuthenticate = true
        //});

        app.UseMvc();
    }
}

AuthorizationInterceptor.ts

import { urls } from "../constants";

class AuthorizationInterceptor {
private static _instance: AuthorizationInterceptor;
private baseUrl: string;

public static Factory($q: angular.IQService, localStorageService, $window) {
    AuthorizationInterceptor._instance = new AuthorizationInterceptor($q, localStorageService, $window);
    return AuthorizationInterceptor._instance;
}

constructor(private $q: angular.IQService, private localStorageService, private $window) {
    console.log("AuthorizationInterceptor created");
}

public request(requestSuccess) {
    const self = AuthorizationInterceptor._instance;
    requestSuccess.headers = requestSuccess.headers || {};

        if (self.localStorageService.get("authorizationData") !== null &&
            self.localStorageService.get("authorizationData") !== "") {
            // when it adds this header i get CORS Error
            requestSuccess.headers.Authorization = "Bearer " + self.localStorageService.get("authorizationData");
        }

        return requestSuccess || self.$q.when(requestSuccess);
}


public responseError(responseFailure) {
    const self = AuthorizationInterceptor._instance;
    console.log("console.log(responseFailure);");
    console.log(responseFailure);

    if (responseFailure.status === 403) {
        alert("forbidden");
        self.$window.location = "https://localhost:44389/forbidden";
        self.$window.href = "forbidden";

    } else if (responseFailure.status === 401) {

        alert("unauthorized");
        self.localStorageService.set("authorizationData", "");
    }

    return self.$q.reject(responseFailure);
}
}

export default AuthorizationInterceptor;

SecurityService.ts

import DataService from "./dataservice";
import { urls } from "../constants";

class SecurityService {
private baseUrl: string;
private isAuthorized: boolean = false;
private hasAdminRole: boolean = false;

constructor(private $http, private $log, private $q, private $rootScope, private $window, private $state,
    private localStorageService, private $stateParams) {
    this.baseUrl = `${urls.baseApiUrl}`;
    console.log("return url: " + this.$stateParams.returnurl);
    $log.info("SecurityService called");

    // what to put this ?
    this.$rootScope.IsAuthorized = false;
    this.$rootScope.HasAdminRole = false;
}

public resetAuthorizationData() {
    this.localStorageService.set("authorizationData", "");
    this.localStorageService.set("authorizationDataIdToken", "");
    this.$rootScope.IsAuthorized = false;
    this.$rootScope.HasAdminRole = false;
}

public setAuthorizationData(token, id_token) {
    if (this.localStorageService.get("authorizationData") !== "") {
        this.localStorageService.set("authorizationData", "");
    }

    this.localStorageService.set("authorizationData", token);
    this.localStorageService.set("authorizationDataIdToken", id_token);
    this.$rootScope.IsAuthorized = true;

    let data: any = this.getDataFromToken(token);
    if (data.role !== undefined) {
        for (let i = 0; i < data.role.length; i++) {
            if (data.role[i] === "dataEventRecords.admin") {
                this.$rootScope.HasAdminRole = true;
            }
        }
    }
}

 public authorize() {
    console.log("AuthorizedController time to log on");

    // GET /authorize?
    // response_type=code%20id_token
    // &client_id=s6BhdRkqt3
    // &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
    // &scope=openid%20profile%data
    // &nonce=n-0S6_WzA2Mj
    // &state=af0ifjsldkj HTTP/1.1

    const authorizationUrl = "https://localhost:44302/connect/authorize";
    const client_id = "angularclient";
    const redirect_uri = "http://localhost:3000/authorized";
    const response_type = "id_token token";
    const scope = "openid profile api1";
    const nonce = "N" + Math.random() + "" + Date.now();
    const state = Date.now() + "" + Math.random();

    this.localStorageService.set("authNonce", nonce);
    this.localStorageService.set("authStateControl", state);
    console.log("AuthorizedController created. adding myautostate: " + this.localStorageService.get("authStateControl"));

    const url =
        authorizationUrl + "?" +
        "response_type=" + encodeURI(response_type) + "&" +
        "client_id=" + encodeURI(client_id) + "&" +
        "redirect_uri=" + encodeURI(redirect_uri) + "&" +
        "scope=" + encodeURI(scope) + "&" +
        "nonce=" + encodeURI(nonce) + "&" +
        "state=" + encodeURI(state);

    this.$window.location = url;
}

public doAuthorization() {
    this.resetAuthorizationData();
    if (this.$window.location.hash) {
        this.authorizeCallback();
    } else {
        this.authorize();
    }
}

public logoff() {
    // var id_token = localStorageService.get("authorizationDataIdToken");
    // var authorizationUrl = 'https://localhost:44330/connect/endsession';
    // var id_token_hint = id_token;
    // var post_logout_redirect_uri = 'https://localhost:44347/unauthorized.html';
    // var state = Date.now() + "" + Math.random();

    // var url =
    //    authorizationUrl + "?" +
    //    "id_token_hint=" + id_token_hint + "&" +
    //    "post_logout_redirect_uri=" + encodeURI(post_logout_redirect_uri) + "&" +
    //    "state=" + encodeURI(state);

    // ResetAuthorizationData();
    // $window.location = url;

    // 19.02.2106: temp until connect/endsession is implemented in IdentityServer4 NOT A PROPER SOLUTION!
    this.resetAuthorizationData();
    this.$window.location = "https://localhost:44389/unauthorized.html";
}

private urlBase64Decode(str) {
    let output = str.replace("-", "+").replace("_", "/");
        switch (output.length % 4) {
            case 0:
                break;
            case 2:
                output += "==";
                break;
            case 3:
                output += "=";
                break;
            default:
                throw "Illegal base64url string!";
        }
        return window.atob(output);
}

private getDataFromToken(token) {
    let data = {};
    if (typeof token !== "undefined") {
        let encoded = token.split(".")[1];
        data = JSON.parse(this.urlBase64Decode(encoded));
    }
    return data;
}

private authorizeCallback() {
    console.log("AuthorizedController created, has hash");
    let hash = window.location.hash.substr(1);

    let result: any = hash.split("&").reduce(function (result2, item) {
        let parts = item.split("=");
        result2[parts[0]] = parts[1];
        return result2;
    }, {});

    let token = "";
    let id_token = "";
    let authResponseIsValid = false;
    if (!result.error) {

        if (result.state !== this.localStorageService.get("authStateControl")) {
            console.log("AuthorizedCallback incorrect state");
        } else {

            token = result.access_token;
            id_token = result.id_token;

            let dataIdToken: any = this.getDataFromToken(id_token);
            console.log(dataIdToken);

            // validate nonce
            if (dataIdToken.nonce !== this.localStorageService.get("authNonce")) {
                console.log("AuthorizedCallback incorrect nonce");
            } else {
                this.localStorageService.set("authNonce", "");
                this.localStorageService.set("authStateControl", "");

                authResponseIsValid = true;
                console.log("AuthorizedCallback state and nonce validated, returning access token");
            }
        }
    }

    if (authResponseIsValid) {
        this.setAuthorizationData(token, id_token);
        console.log(this.localStorageService.get("authorizationData"));

        this.$state.go("overviewindex");
    } else {
        this.resetAuthorizationData();
        this.$state.go("unauthorized");
    }
}
}

SecurityService.$inject = ["$http", "$log", "$q", "$rootScope", "$window",      "$state", "localStorageService", "$stateParams"];
export default SecurityService;



Ответы (3)


Кажется, это проблема CORS. Включите CORS в своей службе, и все будет хорошо. Когда вы добавляете заголовок авторизации, это делает запрос непростым в терминах MDN. И это могло вызвать предварительный запрос OPTIONS. Подробнее о CORS можно прочитать здесь — https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

ОБНОВЛЕНИЕ — у вас есть код для включения CORS в вашем коде, вы его закомментировали. Вместо того, чтобы давать полное разрешение (*), дайте конкретное разрешение для вашего домена

person Developer    schedule 27.08.2016
comment
вместо этого я попробовал это var policy = new Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy(); policy.Headers.Add(); policy.Methods.Add(); политика.Истоки.Добавить(*); policy.SupportsCredentials = true; services.AddCors(x => x.AddPolicy(AllowAll, policy)); Но я получаю ту же ошибку - person n3tx; 27.08.2016
comment
Но когда я удаляю файл cookie авторизации, он работает. Но все еще нужен этот токен - person n3tx; 27.08.2016
comment
CORS работает, когда я не отправляю носитель авторизации. - person n3tx; 27.08.2016
comment
Попробуйте policy.Headers.Add(*) - person Developer; 27.08.2016
comment
Или для тестирования попробуйте только эти строки: var corsAttr = new EnableCorsAttribute(,, *); config.EnableCors(corsAttr); - person Developer; 27.08.2016
comment
@ n3tx - повезло? - person Developer; 27.08.2016
comment
Нет, не работает с policy.Headers.Add(*), а EnableCorsAttribute недоступен в aspnetcore 1.0. У меня нет HttpConfiguration в файле startup.cs - person n3tx; 27.08.2016
comment
У вас нет определенной политики CORS с именем AllowAll, но вы запросили ее использование. Вы закомментировали эту политику. - person Developer; 28.08.2016
comment
Я пробовал разные подходы, и ни один из них не работает. Я не знаю, что делать больше. - person n3tx; 28.08.2016
comment
Почему CORS работает без этого атрибута авторизации? - person n3tx; 28.08.2016
comment
Просто для тестирования попробуйте явно включить cors для этого контроллера и дать полное разрешение... - person Developer; 28.08.2016
comment
Поскольку вы установили пользовательский заголовок.. в cors, вы можете указать разрешенные заголовки, и заголовок, который вы устанавливаете, отсутствует в этом списке. Убедитесь, что разрешенные глаголы также включают глагол OPTIONS. - person Developer; 28.08.2016
comment
Я попробовал это [EnableCors(AllowAll)] поверх метода действия в контроллере. Тоже не сработало. Я попытался добавить cors в методы Configure и ConfigureServices. Тоже не работает. Я не знаю, что попробовать дальше? Все работает, если не включен этот заголовок авторизации. Это странно. - person n3tx; 28.08.2016
comment
Как вы думаете, может быть что-то в перехватчике авторизации работает не так, как должно? - person n3tx; 28.08.2016
comment
Ваш перехватчик в порядке, и он добавляет заголовок авторизации к запросу. Но этот заголовок вызывает предварительный запрос OPTIONS, если запрос является междоменным. Не могли бы вы подтвердить, возникает ли ошибка в этом запросе OPTIONS? - person Developer; 28.08.2016
comment
Метод запроса: Код состояния OPTIONS: 204 Нет содержимого - и метод GET: Метод запроса: Код состояния GET: 500 Внутренняя ошибка сервера - это после того, как я вошел в систему - person n3tx; 28.08.2016
comment
Это означает, что предварительный запрос OPTIONS прошел успешно. Не могли бы вы получить заголовки запроса GET? - person Developer; 28.08.2016
comment
Accept:application/json, text/plain, / Accept-Encoding:gzip, deflate, sdch, br Accept-Language:sv-SE,sv;q=0.8,en-US;q=0.6 ,en;q=0.4 Авторизация: предъявитель eyJhbGciOiJSUzI1NiI.... - person n3tx; 28.08.2016
comment
Я попробовал это с Postman, и я получаю ту же ошибку при добавлении заголовка авторизации. - person n3tx; 28.08.2016
comment
Удалите заголовок авторизации и попробуйте с другим фиктивным заголовком, давайте посмотрим, относится ли он к заголовку авторизации или ко всем пользовательским заголовкам. - person Developer; 28.08.2016
comment
Он работает с фиктивным заголовком. Я попытался добавить знак равенства перед Bearer, и теперь он работает. Но когда я пытаюсь получить данные из метода [Authorize], я получаю 401. Так что, может быть, знак = ничего не делает? - person n3tx; 28.08.2016
comment
Нет.. Я пробовал с любым знаком перед Bearer, и я получаю данные из API, но я не думаю, что он отправляет Bearer.. Так что я не думаю, что это работает. Вот почему я все еще получаю 401 - person n3tx; 28.08.2016
comment
Когда я добавляю знак равенства к = Bearer, я получаю данные из API (только анонимно). Так что = ничего не добавляет. Без = я получаю ошибку cors. - person n3tx; 28.08.2016
comment
Итак, в каком сценарии вы получаете 401? - person Developer; 28.08.2016
comment
Когда я добавляю знак равенства, например = Bearer в перехватчике. Но я не думаю, что он отправляет авторизацию тогда - person n3tx; 28.08.2016
comment
Я имею в виду config.headers['Authorization'] = 'Basic your_token_here' . Просто попытка. - person Developer; 28.08.2016
comment
Я изменил Bearer на Basic в перехватчике. И я не получаю никаких ошибок cors и 401 при попытке доступа к методу авторизации. Так что похоже проблема в Bearer - person n3tx; 28.08.2016
comment
Интересно... кстати, что это за токен, носитель или базовый или jwt токен? И не могли бы вы добавить обновление к своему вопросу с изменениями кода, которые вы сделали, чтобы достичь этого. Было бы читаемо, если бы вы могли удалить комментарии .. - person Developer; 28.08.2016
comment
Хороший вопрос.. Как я могу это увидеть? Я получаю токен от IdentityServer (IdentityServer4). - person n3tx; 28.08.2016
comment
API получил это app.UseJwtBearerAuthentication(jwtBearerOptions); Я так понимаю, это токен JWT? - person n3tx; 28.08.2016

Я думаю, вам нужен пользовательский интерфейс ICorsPolicyService и DI.

public class SwaggerCorsPolicyService : DefaultCorsPolicyService, ICorsPolicyService
{
    private readonly IClientService _clientService;
    private readonly ILogger _logger;

    public SwaggerCorsPolicyService(ILoggerFactory logger, IClientService clientService) : base(logger.CreateLogger<DefaultCorsPolicyService>())
    {
        this._clientService = clientService;
        this._logger = logger.CreateLogger<SwaggerCorsPolicyService>();
    }

    async Task<bool> ICorsPolicyService.IsOriginAllowedAsync(string origin)
    {
        if (AllowAll)
        {
            _logger.LogDebug("AllowAll true, so origin: {0} is allowed", origin);
            return true;
        }

        AllowedOrigins = await _clientService.**Get All Client Uris**;
        if (AllowedOrigins != null)
        {
            if (AllowedOrigins.Contains(origin, StringComparer.OrdinalIgnoreCase))
            {
                _logger.LogDebug("AllowedOrigins configured and origin {0} is allowed", origin);
                return true;
            }
            else
            {
                _logger.LogDebug("AllowedOrigins configured and origin {0} is not allowed", origin);
            }
        }
        _logger.LogDebug("Exiting; origin {0} is not allowed", origin);
        return false;
    }

}

в ConfigureServices

var icps = services.Where(p => p.ServiceType == typeof(ICorsPolicyService)).FirstOrDefault();
if (icps != null) services.Remove(icps);
services.AddTransient<ICorsPolicyService, SwaggerCorsPolicyService>();
person Sim Tsai    schedule 29.08.2016
comment
В каком проекте? IdentityServer или API? - person n3tx; 31.08.2016
comment
Я пробовал как *, так и localhost:3000 .. например, AllowedOrigins = new List‹string› {localhost:3000}; - person n3tx; 31.08.2016
comment
@n3tx в проекте IdentityServer все еще не запущен? - person Sim Tsai; 01.09.2016
comment
Теперь я получаю эту ошибку: Невозможно получить конфигурацию с: 'localhost:44361/.well-known /openid-конфигурация'. - person n3tx; 04.09.2016

Я также столкнулся с этой же проблемой, и после некоторых проблем я понял, что ошибка не имеет ничего общего с CORS.

Вы должны проверить журналы вашего API. В моем случае ошибка заключалась в том, что срок действия токена-носителя истек или произошел сбой какого-либо другого механизма аутентификации, но браузер обнаружил это как проблему CORS из-за отсутствия надлежащего заголовка.

К счастью, IDSRV4 имеет хорошую систему ведения журналов, поэтому вам следует проверить выходные журналы API.

person Miguel A. Arilla    schedule 29.05.2017