如何在asp.net core 2.2中实现Cookie基础身份验证和jwt?

时间:2019-05-11 20:15:56

标签: c# asp.net-core jwt

我想在程序中同时使用基于cookie的身份验证和jwt,使用身份验证用户通过登录名访问mvc控制器,并使用JWT访问WebApi资源。

我尝试使用其中两个:首先,我的客户端可以使用用户名和密码登录并通过cookie进行身份验证。使用带有令牌载体的WebApi从应用程序获得第二次访问资源,但出现错误!

在我的startup.cs文件中,我有:

public void ConfigureServices(IServiceCollection services)
        {


            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
                options.ConsentCookie.Name = "Cookie";
            });
            services.ConfigureApplicationCookie(options =>
            {
                options.Cookie.Name = "Cookie";
                options.ClaimsIssuer = Configuration["Authentication:ClaimsIssuer"];
            });

            services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultUI(UIFramework.Bootstrap4)
                .AddDefaultTokenProviders();

            services.Configure<IdentityOptions>(options =>
            {
                // Password settings.
                options.Password.RequireDigit = true;
                options.Password.RequireLowercase = true;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;
                options.Password.RequiredLength = 5;
                options.Password.RequiredUniqueChars = 1;

                // Lockout settings.
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.AllowedForNewUsers = true;

                // User settings.
                options.User.AllowedUserNameCharacters =
                "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
                options.User.RequireUniqueEmail = false;

                //Token
            });

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;

            })
                .AddCookie(options =>
                {
                    options.Cookie.Name = "Cookie";
                    options.ClaimsIssuer = Configuration["Authentication:ClaimsIssuer"];
                })
                .AddMicrosoftAccount(microsoftOptions =>
                 {
                     microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
                     microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:Password"];
                 })
                .AddGoogle(googleOptions => 
                {
                    googleOptions.ClientId = "XXXXXXXXXXX.apps.googleusercontent.com";
                    googleOptions.ClientSecret = "g4GZ2#...GD5Gg1x";
                    googleOptions.Scope.Add("https://www.googleapis.com/auth/plus.login");
                    googleOptions.ClaimActions.MapJsonKey(ClaimTypes.Gender, "gender");
                    googleOptions.SaveTokens = true;
                    googleOptions.Events.OnCreatingTicket = ctx =>
                    {
                        List<AuthenticationToken> tokens = ctx.Properties.GetTokens()
                            as List<AuthenticationToken>;
                        tokens.Add(new AuthenticationToken()
                        {
                            Name = "TicketCreated",
                            Value = DateTime.UtcNow.ToString()
                        });
                        ctx.Properties.StoreTokens(tokens);
                        return Task.CompletedTask;
                    };
                })
                .AddJwtBearer(options =>
                {
                    options.ClaimsIssuer = Configuration["Authentication:ClaimsIssuer"];
                    options.SaveToken = true;
                    options.Authority = Configuration["Authentication:Authority"];
                    options.Audience = Configuration["Authentication:Audience"];
                    options.RequireHttpsMetadata = false;
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {

                        ValidateIssuerSigningKey = true,

                        ValidateIssuer = true,
                        ValidIssuer = Configuration["Authentication:ValidIssuer"],

                        ValidateAudience = true,
                        ValidAudience = Configuration["Authentication:ValidAudience"],

                        ValidateLifetime = true,

                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:SecurityKey"]))
                    };
                });






            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddSession();

            services.AddSingleton<IConfiguration>(Configuration);

        }

我在此控制器中获得了一个令牌:

[AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> GetToken(TokenLoginModel model)
        {

            if (!ModelState.IsValid) return BadRequest("Token failed to generate");
            var user = await _usermanager.FindByNameAsync(model.UserName);
            //var user = true;// (model.Password == "password" && model.Username == "username");
            if (user != null && await _usermanager.CheckPasswordAsync(user, model.Password))
            {
                var claims = new[]{
                    new Claim("ClaimsIssuer", _configuration.GetSection("Authentication:ClaimsIssuer").Value),
                new Claim(Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames.Sub,user.UserName),
                new Claim(Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString())
            };
                string SecurKey = Startup.StaticConfig.GetSection("Authentication:SecurityKey").Value;
                var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurKey));
                var token = new JwtSecurityToken(
                    issuer: _configuration.GetSection("Authentication:ValidIssuer").Value,
                    audience: _configuration.GetSection("Authentication:Audience").Value,
                    expires: DateTime.UtcNow.AddDays(30),
                    claims: claims,
                    signingCredentials: new Microsoft.IdentityModel.Tokens.SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
                );
                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token),
                    expiration = token.ValidTo
                });
            }
            return Unauthorized();

        }

我实现了创建令牌的控件,但是当我尝试对此进行授权时,出现此错误:

An unhandled exception occurred while processing the request.

HttpRequestException: Response status code does not indicate success: 404 (Not Found).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()

IOException: IDX20804: Unable to retrieve document from: 'https://localhost:44383/oauth2/default/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(string address, CancellationToken cancel)

InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://localhost:44383/oauth2/default/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.ConfigurationManager<T>.GetConfigurationAsync(CancellationToken cancel)

2 个答案:

答案 0 :(得分:1)

为了增加对JWT的支持,我们添加了AddCookie和AddJwtBearer。让网站在标头中要求令牌将是一件令人头疼的事情,尤其是对于并非纯粹是SPA或API的项目。因此,我真正想要的是同时支持Cookie和JWT。

在startup.cs中,您拥有:

    public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }
    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddDbContext<DualAuthContext>(options =>
          options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

      services.AddIdentity<ApplicationUser, IdentityRole>()
          .AddEntityFrameworkStores<DualAuthContext>()
          .AddDefaultTokenProviders();

      // Enable Dual Authentication 
      services.AddAuthentication()
        .AddCookie(cfg => cfg.SlidingExpiration = true)
        .AddJwtBearer(cfg =>
        {
          cfg.RequireHttpsMetadata = false;
          cfg.SaveToken = true;
          cfg.TokenValidationParameters = new TokenValidationParameters()
          {
            ValidIssuer = Configuration["Tokens:Issuer"],
            ValidAudience = Configuration["Tokens:Issuer"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
          };
        });

      // Add application services.
      services.AddTransient<IEmailSender, EmailSender>();
      services.AddMvc();
    }

在“配置”方法中:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, DataSeeder seeder)
{
  ...
  app.UseAuthentication();
}

在控制器中使用了JWT之后,应将JWT Bearer AuthenticationSchemes添加到Authorize属性,如下所示:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
  [Route("/api/customers")]
  public class ProtectedController : Controller
  {
    public ProtectedController()
    {
    }

    public IActionResult Get()
    {
      return Ok(new[] { "One", "Two", "Three" });
    }
  }

引用:Two AuthorizationSchemes in ASP.NET Core 2

使用起来非常简单而且很有帮助。

答案 1 :(得分:0)

以下是我使用OpenIdConnect进行的配置 在您的startup.cs

配置

    app.UseCookiePolicy();

ConfigureServices

services
    .AddIdentity<User, ApplicationRole>(options =>
    {
        options.Password.RequireDigit = false;
        options.Password.RequiredLength = 4;
        options.Password.RequireLowercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = false;

        //lock out attempt
        options.Lockout.AllowedForNewUsers = true;
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
        options.Lockout.MaxFailedAccessAttempts = 3;
    })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

services.Configure<CookiePolicyOptions>(options =>
{
    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
    options.CheckConsentNeeded = context => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
});

//The default value is 14 days.
services.ConfigureApplicationCookie(options =>
{
    options.ExpireTimeSpan = TimeSpan.FromHours(1);
});

// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
    options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
    options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
    options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});

services.AddOpenIddict()
    // Register the OpenIddict core services.
    .AddCore(options =>
    {
        // Register the Entity Framework stores and models.
        options.UseEntityFrameworkCore()
            .UseDbContext<ApplicationDbContext>();
    })
    // Register the OpenIddict server handler.
    .AddServer(options =>
    {
        // Register the ASP.NET Core MVC binder used by OpenIddict.
        // Note: if you don't call this method, you won't be able to
        // bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
        options.UseMvc();

        // Enable the token endpoint.
        options.EnableTokenEndpoint("/connect/token")
            .EnableAuthorizationEndpoint("/connect/authorize")
            .EnableLogoutEndpoint("/connect/logout")
            .EnableIntrospectionEndpoint("/connect/introspect")
            .EnableUserinfoEndpoint("/connect/userinfo");

        // Enable the password and the refresh token flows.
        options.AllowPasswordFlow()
            .AllowRefreshTokenFlow();

        // Accept anonymous clients (i.e clients that don't send a client_id).
        options.AcceptAnonymousClients();

        // During development, you can disable the HTTPS requirement.
        options.DisableHttpsRequirement();

        // Note: to use JWT access tokens instead of the default
        // encrypted format, the following lines are required:
        //
        options.UseJsonWebTokens();
        options.AddEphemeralSigningKey();

        options.SetAccessTokenLifetime(TimeSpan.FromMinutes(60))
            .SetRefreshTokenLifetime(TimeSpan.FromMinutes(60));
    });

// Register the OpenIddict validation handler.
// Note: the OpenIddict validation handler is only compatible with the
// default token format or with reference tokens and cannot be used with
// JWT tokens. For JWT tokens, use the Microsoft JWT bearer handler.
//.AddValidation();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();

services.AddAuthentication()
        .AddJwtBearer(options =>
        {
            options.Authority = configuration["Authentication:Authority"];
            options.Audience = "resource_server";
            options.RequireHttpsMetadata = false;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = OpenIdConnectConstants.Claims.Subject,
                RoleClaimType = OpenIdConnectConstants.Claims.Role
            };
        });

// Alternatively, you can also use the introspection middleware.
// Using it is recommended if your resource server is in a
// different application/separated from the authorization server.
//
// services.AddAuthentication()
//     .AddOAuthIntrospection(options =>
//     {
//         options.Authority = new Uri("http://localhost:54895/");
//         options.Audiences.Add("resource_server");
//         options.ClientId = "resource_server";
//         options.ClientSecret = "875sqd4s5d748z78z7ds1ff8zz8814ff88ed8ea4z4zzd";
//         options.RequireHttpsMetadata = false;
//     });