如何在无UI的API控制器中使用EF Core Identity认证授权功能?
基于EF Core Identity自定义前后端分离API的用户认证方案
完全可以借助UserManager、SignInManager等EF Core Identity组件安全处理用户认证,结合前后端分离的场景,JWT确实是最适配的方案——因为Session依赖Cookie,跨域场景下配置繁琐且灵活性不足,而JWT是无状态令牌,更适合API架构。以下是具体实现步骤:
一、核心配置(Program.cs)
先在API项目中配置Identity服务和JWT认证:
using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; using System.Text; var builder = WebApplication.CreateBuilder(args); // 注册自定义Identity用户和DbContext builder.Services.AddIdentityCore<CustomUser>(options => { // 可根据需求配置密码、登录策略 options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.SignIn.RequireConfirmedAccount = false; }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // 配置JWT认证 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, // 从配置文件读取JWT参数(可在appsettings.json中配置) ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); // 添加授权服务 builder.Services.AddAuthorization(); // 注册控制器和跨域(根据React前端地址配置) builder.Services.AddControllers(); builder.Services.AddCors(options => { options.AddPolicy("AllowReact", policy => { policy.WithOrigins("http://localhost:3000") // 替换为你的React地址 .AllowAnyHeader() .AllowAnyMethod(); }); }); var app = builder.Build(); // 中间件顺序:先跨域,再认证,再授权,最后控制器 app.UseCors("AllowReact"); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
二、编写自定义认证接口(AuthController)
注入UserManager和SignInManager,实现注册、登录逻辑:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; [ApiController] [Route("api/auth")] public class AuthController : ControllerBase { private readonly UserManager<CustomUser> _userManager; private readonly SignInManager<CustomUser> _signInManager; private readonly IConfiguration _config; public AuthController(UserManager<CustomUser> userManager, SignInManager<CustomUser> signInManager, IConfiguration config) { _userManager = userManager; _signInManager = signInManager; _config = config; } // 注册接口 [HttpPost("register")] public async Task<IActionResult> Register([FromBody] RegisterRequest request) { var user = new CustomUser { UserName = request.Username, Email = request.Email }; var createResult = await _userManager.CreateAsync(user, request.Password); if (!createResult.Succeeded) { return BadRequest(createResult.Errors.Select(e => e.Description)); } return Ok(new { Message = "注册成功" }); } // 登录接口 [HttpPost("login")] public async Task<IActionResult> Login([FromBody] LoginRequest request) { var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { return Unauthorized("用户名或密码错误"); } // 验证密码(Identity自动处理密码哈希,无需手动加密) var signInResult = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: false); if (!signInResult.Succeeded) { return Unauthorized("用户名或密码错误"); } // 生成JWT令牌 var token = GenerateJwtToken(user); return Ok(new { AccessToken = token, ExpiresIn = 3600 }); // 1小时过期 } // 生成JWT令牌的私有方法 private string GenerateJwtToken(CustomUser user) { var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Email, user.Email) // 可添加自定义Claim,比如角色、权限 }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _config["Jwt:Issuer"], audience: _config["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } } // 请求模型 public class RegisterRequest { public string Username { get; set; } public string Email { get; set; } public string Password { get; set; } } public class LoginRequest { public string Username { get; set; } public string Password { get; set; } }
三、React前端处理逻辑
登录成功后存储JWT令牌,后续请求在请求头中携带令牌:
// 登录请求示例 const handleLogin = async (username, password) => { try { const response = await fetch('http://localhost:5000/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (!response.ok) throw new Error('登录失败'); const data = await response.json(); // 存储令牌(生产环境建议用内存存储,比如React Context,避免XSS风险) localStorage.setItem('accessToken', data.AccessToken); // 跳转到首页或其他授权页面 } catch (err) { alert(err.message); } }; // 带令牌的请求示例(访问受保护接口) const fetchProtectedData = async () => { const token = localStorage.getItem('accessToken'); try { const response = await fetch('http://localhost:5000/api/protected', { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('无访问权限'); const data = await response.json(); // 处理返回数据 } catch (err) { // 令牌过期或无效,跳转到登录页 localStorage.removeItem('accessToken'); window.location.href = '/login'; } };
四、关于Session模式的说明
如果非要用SignInManager的Session认证,也可以实现,但仅适合同域场景:
- 在Program.cs中配置Cookie认证和Session:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.HttpOnly = true; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); builder.Services.AddSession(options => options.IdleTimeout = TimeSpan.FromMinutes(30));
- 登录接口中调用
await _signInManager.SignInAsync(user, isPersistent: false); - 前端请求时需设置
withCredentials: true,且跨域配置要允许AllowCredentials
但这种方式在跨域、移动端场景下灵活性差,不推荐用于前后端分离架构。
五、安全注意事项
- 生产环境必须使用HTTPS,防止令牌被窃取
- 令牌过期时间不宜过长,可配合刷新Token机制优化用户体验
- 前端存储令牌尽量避免localStorage(易受XSS攻击),推荐用内存存储或HttpOnly Cookie存刷新Token
- 可通过
UserManager添加角色、权限等自定义逻辑,结合[Authorize(Roles = "Admin")]保护接口
内容的提问来源于stack exchange,提问作者m3.b
相关产品推荐
相关产品推荐

