如何在洋葱架构下合理使用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
相关产品推荐
相关产品推荐

