ASP.NET 核心 - 将 Cookie 和 JWT 用于 WebAPI

ASP.NET Core - Using both Cookies and JWT for WebAPI

我已经为我的 ASP.NET Core Web API 配置了 JWT 身份验证。使用 Postman 时有效。

我还构建了一个 MVC 管理部分,我想登录它。我遵循的创建管理部分的指南使用 cookie 而不是 JWT 身份验证用于登录页面。

不行,登录后提示401认证错误。它会将我重定向到正确的页面,您可以在浏览器中看到身份 cookie,但我未通过身份验证。

我这里有点力不从心哈哈

我可以同时使用 cookie 和 JWT 身份验证吗? JWT 适用于任何想要访问 WebAPI 的移动 phone 应用程序,但用于通过 WebAPI 管理页面登录的 Cookie 和会话?

我的中间件Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
        // Tell Entity how to connect to the SQL Server
        services.AddDbContext<ApplicationDbContext>(options => 
        {
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
        });

        // Configure Identity
        services.Configure<IdentityOptions>(options =>
        {
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
            options.Lockout.MaxFailedAccessAttempts = 5;
            options.Lockout.AllowedForNewUsers = true;
            options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
            options.SignIn.RequireConfirmedEmail = false;                   // Set to true for production, test it
            options.User.RequireUniqueEmail = false;                        // Set to true for production
        });

        services.Configure<PasswordHasherOptions>(options =>
        {
            // First byte of the hashed password is 0x00 = V2 and 0x01 = V3
            options.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV3;        // Default IdentityV2 is used, it uses SHA1 for hashing, 1000 iterations.
            options.IterationCount = 12000;                                                // With IdentityV3 we can use SHA256 and 12000 iterations.
        });

        // We need to add the IdentityUser to Entity and create a token for authentication.
        services.AddIdentity<User, IdentityRole>(options =>
        {
            options.Password.RequireDigit = true;
            options.Password.RequireLowercase = true;
            options.Password.RequireUppercase = true;
            options.Password.RequiredLength = 6;

        }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();


        // JWT Authentication Tokens
        services.AddAuthentication(auth =>
       {
           // This will stop Identity using Cookies and make it use JWT tokens by default.
           auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
           auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
       }).AddJwtBearer(options =>
       {
           options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
           {
               ValidateIssuer = true,
               ValidateAudience = true,
               ValidAudience = "http://mywebsite.com",
               ValidIssuer = "http://mywebsite.com",
               ValidateLifetime = true,
               RequireExpirationTime = true,
               ValidateIssuerSigningKey = true,
               IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rsvgy555262gthsdfrthga"))
           };
           options.RequireHttpsMetadata = true;                    // Use HTTPS to transmit the token.
       });

        // Admin Login Cookie
        services.ConfigureApplicationCookie(options =>
        {
            options.LoginPath = "/Admin/Login";                             // Url for users to login to the app
            options.Cookie.Name = ".AspNetCore.Identity.Application";
            options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
            options.SlidingExpiration = true;
        });

        services.AddControllers();
        services.AddControllersWithViews();

}

我的AdminController:

public class AdminController : Controller
{
    private UserManager<User> userManager;                  // Manage user accounts in DB
    private IPasswordHasher<User> passwordHasher;           // Hash user passwords
    private SignInManager<User> signInManager;              // Login

    // Constructor
    public AdminController(UserManager<User> usrMgr, IPasswordHasher<User> passwordHash, SignInManager<User> signinMgr)
    {
        userManager = usrMgr;
        passwordHasher = passwordHash;
        signInManager = signinMgr;
    }

    // Admin Login Page
    [AllowAnonymous]
    public IActionResult Login(string returnUrl)
    {
        Login login = new Login();
        return View(login);
    }

    // Admin Login Module
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(Login login)
    {
        if (ModelState.IsValid)
        {
            User loginUser = await userManager.FindByEmailAsync(login.Email);

            if (loginUser != null)
            {
                // Sign out any user already signed in
                await signInManager.SignOutAsync();

                // Sign in the new user
                Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(loginUser, login.Password, false, false);
                if (result.Succeeded)
                {
                    return Redirect("/Admin"); // Send user to localhost/Admin after login
                }
            }

            ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");
        }

        return View(login);
    }

    // Admin Logout
    public async Task<IActionResult> Logout()
    {
        await signInManager.SignOutAsync();
        return RedirectToAction("Index");
    }

    // Admin Index Page
    [Authorize]
    public IActionResult Index()
    {
        return View(userManager.Users);
    }
}

谢谢,如果您能帮助我们使用 cookie,我们将不胜感激。

我找到了 2 种方法来完成这个:Token Biased 和 Cookie Biased(首选)。

我正在使用 ASP.NET Core 5.0,顺便说一句,这可能不适用于 3.1。

令牌偏向解决方案

services.AddAuthentication(x => {
    // Set Jwt Bearer as default auth scheme.
    // Token found in Authorization header by default (Authorization: Bearer <JWT_TOKEN>)
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x => {
    x.SaveToken = true;

    // Tap into the event lifecycle
    x.Events = new JwtBearerEvents {

        // Called when default authentication fails
        // This is where we validate add cookie authentication
        OnAuthenticationFailed = ctx => {
            string token = ctx.HttpContext.Request.Cookies["auth"];
            
            if (string.IsNullOrEmpty(token)) {
                // Tells ASP.NET that authentication failed
                ctx.Fail("Invalid token");

            } else {

                // Validate token
                if (JwtManager.ValidateToken(token, config)) {

                    // Set the principal
                    // Will throw error if not set
                    ctx.Principal = JwtManager.GetPrincipal(token);

                    // Tells ASP.NET that the authentication was successful
                    ctx.Success();

                    // Add the principal to the HttpContext for easy access in any the controllers (only routes that are authenticated)
                    ctx.HttpContext.Items.Add("claims", principal);
                } else {
                    ctx.Fail("Invalid Token");
                }
            }
            return Task.CompletedTask;
        }
    };

    ...
});

JwtManager 只是一个处理 JWT 操作的 class。本质上,它包含用于生成、验证和解码 JWT 令牌的静态方法。每种方法都有不同的方法,具体取决于图书馆。

仅当默认令牌验证失败时才会调用此事件。

注意 1: 使用此解决方案,您仍然需要在 headers 中包含 Authoritization: Bearer <SOMETHING>

Cookie 偏向解决方案 本质上是一样的,但是在 OnMessageRecieved 事件中

services.AddAuthentication(x => {
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x => {
    x.SaveToken = true;
    x.Events = new JwtBearerEvents {
        // Same as before, but called before the default JWT bearer authentication
        OnMessageReceived = ctx => {
            string token = ctx.HttpContext.Request.Cookies["auth"];
            
            if (!string.IsNullOrEmpty(token)) {
                if (JwtManager.ValidateToken(token, config)) {
                    var principal = JwtManager.GetClaims(token);
                    ctx.Principal = principal;
                    ctx.Success();
                    ctx.HttpContext.Items.Add("claims", principal);
                }
            }
            
            return Task.CompletedTask;
        },
    };
});

每个请求都会调用它,因此它可能会有一些开销,但它仍然有效。

注意 2: 这些解决方案可能未准备好生产,甚至不是最好的方法。与一粒盐一起使用。