c# - ASP.NET Core - 对 WebAPI 使用 Cookie 和 JWT

标签 c# asp.net-core asp.net-web-api asp.net-identity asp.net-authentication

我已成功为 ASP.NET Core Web API 配置 JWT 身份验证。它在使用 Postman 时有效。

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

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

Login Page

Admin Page

我已经超出了我的深度,哈哈

我还可以使用 cookie 以及 JWT 身份验证吗? JWT 适用于任何想要访问 WebAPI 但需要通过 WebAPI 管理页面登录的 Cookie 和 session 的手机应用程序吗?

我的中间件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 正常工作,我们将不胜感激。

最佳答案

我发现了两种实现此目的的方法:Token Biased 和 Cookie Biased(首选)。

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

token 偏向解决方案

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 操作的类。本质上,它拥有用于生成、验证和解码 JWT token 的静态方法。根据库的不同,每种方法都有不同的方法。

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

注 1: 使用此解决方案,您仍然需要 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: 这些解决方案可能尚未准备好用于生产,甚至不是实现生产的最佳方法。与一粒盐一起使用。

关于c# - ASP.NET Core - 对 WebAPI 使用 Cookie 和 JWT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67757020/

相关文章:

angularjs - 如果不存在, Breeze 获取元数据

c# - 为什么 C# 编译器不内联 MSIL 代码?

c# - 什么 .Net 数据类型会触发无法将类型为 '' 的对象转换为类型 'System.String'

c# - 在 Entity Framework 中记录插入和更新命令 sql 查询

c# - 将类的对象声明为 null

c# - asp.net core razor pages 支持删除和放置请求

c# - SupportedUICultures 不显示所有文化

linux - Google 云平台 (Linux) 上的 ASP.NET Core 应用

asp.net - 如何包含多个子对象?

c# - 与 AngularJS/.NET Web API 应用程序串联的 RabbitMQ 架构