Asp.Net Core, JWT и OpenIdConnectServer

Я пытаюсь понять все эти вещи о токенах JWT в Asp.Net Core. И после прокачки работы с огромным количеством недоразумений я застрял. Моя задача сделать сервер WebApi с двумя контроллерами (защищенный и не защищенный). Я должен выдавать токены с сервера и иметь возможность получать защищенные ресурсы. кажется, что все в порядке, когда я запускаю сервер и пытаюсь получить защищенные ресурсы от почтальона. Но когда я делаю то же самое в моем клиенте Angular, из другого домена у меня возникает странная вещь. Я могу получить незащищенный ресурс, и я не могу получить защищенный ресурс с такой ошибкой:

XMLHttpRequest cannot load http://localhost:10450/api/prvalues. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:10377' is therefore not allowed access.

Чтобы было понятно, я показываю все свои попытки. Мой проект.json выглядит так

  "dependencies": {
     "AspNet.Security.OpenIdConnect.Server": "1.0.0-beta4",
     "EntityFramework.Core": "7.0.0-rc1-final",
     "EntityFramework.InMemory": "7.0.0-rc1-final",
     "Microsoft.AspNet.Authentication.JwtBearer": "1.0.0-rc1-final",
     "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final",
     "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-rc1-final",
     "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
     "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
     "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
     "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
     "Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc1-final",
     "Microsoft.Extensions.Logging": "1.0.0-rc1-final",
     "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
     "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
     "NWebsec.Middleware": "1.0.0-gamma-39",
     "Microsoft.AspNet.Mvc.Cors": "6.0.0-rc1-final",
     "Microsoft.AspNet.Cors": "6.0.0-rc1-final"
},

Мой Startup.ConfigureServices() выглядит так

public void ConfigureServices (службы IServiceCollection) {

        services.AddCors(options =>
        {
            options.AddPolicy("AllowAllOrigins",
                    builder =>
                    {
                        builder.AllowAnyOrigin();
                        builder.AllowAnyHeader();
                    });
        });

        services.AddEntityFramework()
           .AddInMemoryDatabase()
           .AddDbContext<ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>>(options => {
               options.UseInMemoryDatabase();
           });
        services.AddScoped<IAuthStore<ApplicationUser,Application>, AuthStore<ApplicationUser, Application, IdentityRole, 
            ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>, string>>();
        services.AddScoped<AuthManager<ApplicationUser, Application>>();
        services.AddIdentity<ApplicationUser, IdentityRole>(options =>
        {
            options.Password = new PasswordOptions()
            {
                RequiredLength = 1,
                RequireDigit = false,
                RequireLowercase = false,
                RequireUppercase = false,
                RequireNonLetterOrDigit = false

            };
        }).AddEntityFrameworkStores<ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>>().AddDefaultTokenProviders();
        services.AddAuthentication();

        // Add framework services.

        services.AddCaching();
        services.AddMvc();
    }

AuthManager и AuthStore украдены у OpenIddict. Я покажу их позже. Мой Startup.Configure() выглядит так:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
        app.UseCors("AllowAllOrigins");
        app.UseIISPlatformHandler();
        app.UseDeveloperExceptionPage();
        app.UseStaticFiles();

        // Create a new branch where the registered middleware will be executed only for API calls.            

        app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
        {
            branch.UseJwtBearerAuthentication(options =>
            {

                options.AutomaticAuthenticate = true;
                options.AutomaticChallenge = true;
                options.RequireHttpsMetadata = false;
                // Thisi is test, if I uncomment this and SetResource in AuthorizationProvider everything works in postman
                //options.Audience = "http://localhost:10450/";
                // My Angular client
                options.Audience = "http://localhost:10377/";
                // My Api
                options.Authority = "http://localhost:10450/";

            });
        });



        // Note: visit https://docs.nwebsec.com/en/4.2/nwebsec/Configuring-csp.html for more information.
        app.UseCsp(options => options.DefaultSources(configuration => configuration.Self())
                                     .ImageSources(configuration => configuration.Self().CustomSources("data:"))
                                     .ScriptSources(configuration => configuration.UnsafeInline())
                                     .StyleSources(configuration => configuration.Self().UnsafeInline()));

        app.UseXContentTypeOptions();

        app.UseXfo(options => options.Deny());

        app.UseXXssProtection(options => options.EnabledWithBlockMode());

        app.UseOpenIdConnectServer(options =>
        {

            options.Provider = new AuthorizationProvider();
            // Note: see AuthorizationController.cs for more
            // information concerning ApplicationCanDisplayErrors.
            options.ApplicationCanDisplayErrors = true;
            options.AllowInsecureHttp = true;
            options.AuthorizationEndpointPath = PathString.Empty;
            options.TokenEndpointPath = "/token";
            // Note: by default, tokens are signed using dynamically-generated
            // RSA keys but you can also use your own certificate:
            // options.SigningCredentials.AddCertificate(certificate);
        });

        app.UseMvc();
        var hasher = new PasswordHasher<Application>();
        using (var database = app.ApplicationServices.GetService<ApplicationDbContext<ApplicationUser, Application, IdentityRole, string>>())
        {              
            database.Applications.Add(new Application
            {
                Id = "myPublicClient",
                DisplayName = "My client application",
                Type = ApplicationTypes.Public
            });
            database.Applications.Add(new Application
            {
                Id = "myConfidentialClient",
                DisplayName = "My client application",
                Secret = hasher.HashPassword(null, "secret_secret_secret"),
                Type = ApplicationTypes.Confidential
            });

            database.SaveChanges();
            CreateUser(app).Wait();
        }        
    }

И, наконец, мой AthorizationProvider.GrantResourceOwnerCredentials() (я использую AspNet.Security.OpenIdConnect.Server):

    public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
    {

        #region UserChecking
        var manager = context.HttpContext.RequestServices.GetRequiredService<AuthManager<ApplicationUser, Application>>();

        var user = await manager.FindByNameAsync(context.UserName);
        if (user == null)
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidGrant,
                description: "Invalid credentials.");

            return;
        }

        // Ensure the user is not already locked out.
        if (manager.SupportsUserLockout && await manager.IsLockedOutAsync(user))
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidGrant,
                description: "Account locked out.");

            return;
        }

        // Ensure the password is valid.
        if (!await manager.CheckPasswordAsync(user, context.Password))
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidGrant,
                description: "Invalid credentials.");

            if (manager.SupportsUserLockout)
            {
                await manager.AccessFailedAsync(user);

                // Ensure the user is not locked out.
                if (await manager.IsLockedOutAsync(user))
                {
                    context.Rejected(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "Account locked out.");
                }
            }

            return;
        }

        if (manager.SupportsUserLockout)
        {
            await manager.ResetAccessFailedCountAsync(user);
        }


        if (context.Request.ContainsScope(OpenIdConnectConstants.Scopes.Profile) &&
           !context.Request.ContainsScope(OpenIdConnectConstants.Scopes.Email) &&
            string.Equals(await manager.GetUserNameAsync(user),
                          await manager.GetEmailAsync(user),
                          StringComparison.OrdinalIgnoreCase))
        {
            context.Rejected(
                error: OpenIdConnectConstants.Errors.InvalidRequest,
                description: "The 'email' scope is required.");

            return;
        }

        #endregion

        var identity = await manager.CreateIdentityAsync(user, context.Request.GetScopes());

        var ticket = new AuthenticationTicket(
           new ClaimsPrincipal(identity),
           new AuthenticationProperties(),
           context.Options.AuthenticationScheme);

        //ticket.SetResources(context.Request.GetResources());
        // When I tested with postman
        //ticket.SetResources(new[] { "http://localhost:10450/" });
        ticket.SetResources(new[] { "http://localhost:10377" });
        ticket.SetScopes(context.Request.GetScopes());

        context.Validated(ticket);


    }

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

Для доступа к моему ресурсу protecetd (просто украшенному [Authorize]) я использую такой фиктивный код из своего клиента Angular

$http.defaults.headers.common['Authorization'] = 'Bearer ' + 'token here';
return $http.get('http://localhost:10450/' + 'api/prvalues');

Как я уже сказал, у меня есть ошибка cors для такого запроса. Но если я пытаюсь получить незащищенный ресурс (удалить атрибут [Authorize] из контроллера), все работает нормально.


person Stalso    schedule 04.03.2016    source источник
comment
Вы пытались использовать AllowCredentials() вместе с AllowAnyMethod(), как в примере Cors на github? github.com/aspnet/Mvc/ блоб/   -  person Tseng    schedule 04.03.2016
comment
Да. Это не имеет никакого эффекта   -  person Stalso    schedule 04.03.2016


Ответы (1)


Я нашел решение. Когда я устанавливаю ресурсы в своем AthorizationProvider.GrantResourceOwnerCredentials(), я устанавливаю "http://localhost:10377" , но в промежуточном программном обеспечении UseJwtBearerAuthentication я устанавливаю "http://localhost:10377/" в параметре полномочий (косая черта включена в конце). Это очень глупая ошибка.

person Stalso    schedule 04.03.2016