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

如何在洋葱架构下合理使用ASP.NET Identity构建出租车应用后端?

我之前在做类似的出租车项目时,刚好也碰到过洋葱架构下整合ASP.NET Identity的难题——既要遵循依赖倒置、领域层纯净的原则,又要利用Identity成熟的身份验证能力。下面是我落地过的一套可行方案,你可以参考:

核心原则适配:把ASP.NET Identity隔离到基础设施层

洋葱架构的核心是内层不依赖外层,领域层作为核心只能依赖抽象,不能直接引用ASP.NET Identity的具体类(比如UserManager、IdentityUser)。所以我们要把所有Identity相关的实现都放到最外层的基础设施层,领域层只定义抽象接口。

1. 领域层定义抽象接口与纯净实体

先在领域层定义用户相关的抽象接口,以及自己的User实体(不要直接用Identity的ApplicationUser):

// 领域层 - 抽象接口
public interface IUserRepository
{
    Task<Domain.User> GetUserByEmailAsync(string email);
    Task<bool> ValidateCredentialsAsync(string email, string password);
    Task<IEnumerable<string>> GetUserRolesAsync(Domain.User user);
}

public interface IAuthTokenService
{
    Task<string> GenerateJwtTokenAsync(Domain.User user, IEnumerable<string> roles);
}

// 领域层 - 纯净用户实体
public class User
{
    public Guid Id { get; set; }
    public string Email { get; set; }
    public string FullName { get; set; }
    // 只保留领域关心的属性,不要包含Identity的冗余字段
}

2. 基础设施层实现Identity适配

在基础设施层创建Identity的上下文和用户实体,然后实现领域层的抽象接口,用Identity的UserManager、SignInManager完成具体操作,同时做好领域实体和Identity实体的映射:

// 基础设施层 - Identity用户实体
public class ApplicationUser : IdentityUser<Guid>
{
    public string FullName { get; set; }
    // 对应领域User的字段,用于映射
}

// 基础设施层 - Identity上下文
public class AppIdentityDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
{
    public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options) : base(options) { }
}

// 基础设施层 - 实现领域接口
public class IdentityUserRepository : IUserRepository
{
    private readonly UserManager<ApplicationUser> _userManager;

    public IdentityUserRepository(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task<Domain.User> GetUserByEmailAsync(string email)
    {
        var appUser = await _userManager.FindByEmailAsync(email);
        return appUser == null ? null : MapToDomainUser(appUser);
    }

    public async Task<bool> ValidateCredentialsAsync(string email, string password)
    {
        var appUser = await _userManager.FindByEmailAsync(email);
        if (appUser == null) return false;
        return await _userManager.CheckPasswordAsync(appUser, password);
    }

    // 领域实体与Identity实体的映射方法
    private Domain.User MapToDomainUser(ApplicationUser appUser)
    {
        return new Domain.User
        {
            Id = appUser.Id,
            Email = appUser.Email,
            FullName = appUser.FullName
        };
    }
}

3. JWT授权与API层适配

因为你的前后端是分离的,用JWT做身份验证是最优选择。把JWT的生成逻辑放到基础设施层的IAuthTokenService实现里,API层只负责暴露登录端点、配置授权中间件:

基础设施层实现JWT生成

public class JwtTokenService : IAuthTokenService
{
    private readonly IConfiguration _config;

    public JwtTokenService(IConfiguration config)
    {
        _config = config;
    }

    public async Task<string> GenerateJwtTokenAsync(Domain.User user, IEnumerable<string> roles)
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Email, user.Email)
        };

        // 添加角色声明
        claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:SecretKey"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.Now.AddHours(8),
            SigningCredentials = creds,
            Issuer = _config["Jwt:Issuer"],
            Audience = _config["Jwt:Audience"]
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

API层配置与端点授权

在Program.cs中注册Identity、JWT服务,以及领域接口和基础设施实现的依赖注入:

// 注册Identity上下文
builder.Services.AddDbContext<AppIdentityDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// 配置Identity
builder.Services.AddIdentity<ApplicationUser, IdentityRole<Guid>>()
    .AddEntityFrameworkStores<AppIdentityDbContext>()
    .AddDefaultTokenProviders();

// 注册领域接口到基础设施实现
builder.Services.AddScoped<IUserRepository, IdentityUserRepository>();
builder.Services.AddScoped<IAuthTokenService, JwtTokenService>();

// 配置JWT授权
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]))
        };
    });

// 启用授权
builder.Services.AddAuthorization();

然后在API控制器中实现登录端点,并给需要授权的接口添加[Authorize]属性:

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly IUserRepository _userRepo;
    private readonly IAuthTokenService _tokenService;

    public AuthController(IUserRepository userRepo, IAuthTokenService tokenService)
    {
        _userRepo = userRepo;
        _tokenService = tokenService;
    }

    [HttpPost("login")]
    [AllowAnonymous]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        var user = await _userRepo.GetUserByEmailAsync(request.Email);
        if (user == null || !await _userRepo.ValidateCredentialsAsync(request.Email, request.Password))
        {
            return Unauthorized("Invalid email or password");
        }

        var roles = await _userRepo.GetUserRolesAsync(user);
        var token = await _tokenService.GenerateJwtTokenAsync(user, roles);
        return Ok(new { Token = token, User = user });
    }
}

// 需要授权的示例控制器
[ApiController]
[Route("api/trips")]
[Authorize(Roles = "Driver")] // 仅允许司机角色访问
public class TripsController : ControllerBase
{
    // 接口实现...
}

4. 前端适配(React/React Native)

前端只需要处理JWT的获取与携带:

  • 登录时调用/api/auth/login端点,获取token后存储到localStorage(Web)或AsyncStorage(React Native)
  • 后续所有请求的请求头中添加Authorization: Bearer {token}
  • 可以封装一个请求拦截器自动添加这个头,同时处理token过期的情况
关键注意事项
  • 领域层绝对不能引用Microsoft.AspNetCore.Identity包,保持领域层的纯净性,只依赖自身的抽象和实体
  • 所有Identity相关的具体操作(比如创建用户、修改密码)都要封装到基础设施层的实现中,通过领域接口暴露给领域层使用
  • 自定义授权规则时,把规则逻辑放到领域层,授权处理器放到基础设施层,避免API层直接依赖领域细节

内容的提问来源于stack exchange,提问作者user9604344

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:55:59