You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在无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认证,也可以实现,但仅适合同域场景:

  1. 在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));
  1. 登录接口中调用await _signInManager.SignInAsync(user, isPersistent: false);
  2. 前端请求时需设置withCredentials: true,且跨域配置要允许AllowCredentials

但这种方式在跨域、移动端场景下灵活性差,不推荐用于前后端分离架构。

五、安全注意事项

  • 生产环境必须使用HTTPS,防止令牌被窃取
  • 令牌过期时间不宜过长,可配合刷新Token机制优化用户体验
  • 前端存储令牌尽量避免localStorage(易受XSS攻击),推荐用内存存储或HttpOnly Cookie存刷新Token
  • 可通过UserManager添加角色、权限等自定义逻辑,结合[Authorize(Roles = "Admin")]保护接口

内容的提问来源于stack exchange,提问作者m3.b

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 13:23:17