Day23 登录 + 颁发 Token(DDD 四层架构 + 企业标准)
一、今日核心内容密码加密BCrypt—— 企业绝对标准不能明文存密码登录接口逻辑登录成功 → 颁发 JWT Token返回前端Token 用户信息VO全部写在DDD 四层架构内二、高频面试题 答案1. 密码为什么不能用 SHA256要用什么答案SHA256 是摘要算法速度太快容易被暴力破解。企业必须用 BCrypt / Argon2自适应哈希慢、加盐、防暴力破解。2. BCrypt 需要自己维护盐值吗答案不需要BCrypt自动生成盐、自动存在哈希里验证时自动提取。3. 登录流程是什么接收账号密码查询用户BCrypt 验证密码生成 JWT Token返回 Token4. JWT 存什么存非敏感信息UserId、Account、RoleIds、RoleCodes绝对不存密码。三、项目结构严格 DDD 四层API # 接口、Controller Application # 用例、Service、DTO、VO Domain # 实体、仓储接口 Infrastructure # 技术实现JWT、BCrypt、仓储实现、EF今天所有代码都放在这 2 个位置1. 工具类BCrypt JWTInfrastructure/ Commons/ Jwt/ JwtService.cs ✅ JWT 服务 Security/ BCryptUtil.cs ✅ 密码加密2. 登录逻辑Application/ Services/ UserService.cs ✅ 写登录逻辑四、第一步安装包BCrypt.Net-Next Microsoft.IdentityModel.Tokens System.IdentityModel.Tokens.Jwt五、代码直接复制即用1. Infrastructure/Commons/Security/BCryptUtil.csusing BCrypt.Net; namespace Admin.NET.Infrastructure.Commons.Security; public static class BCryptUtil { /// summary /// 加密密码 /// /summary public static string HashPassword(string password) { return BCrypt.Net.BCrypt.HashPassword(password); } /// summary /// 验证密码 /// /summary public static bool Verify(string password, string hash) { return BCrypt.Net.BCrypt.Verify(password, hash); } }2. Infrastructure/Commons/Jwt/JwtService.csusing System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Admin.NET.Domain.Entities; using Microsoft.IdentityModel.Tokens; namespace Admin.NET.Infrastructure.Commons.Jwt; public class JwtService { private readonly string _secret; private readonly string _issuer; private readonly string _audience; private readonly int _expireMinutes; public JwtService(string secret, string issuer, string audience, int expireMinutes) { _secret secret; _issuer issuer; _audience audience; _expireMinutes expireMinutes; } public string GenerateToken(User user) { var claims new ListClaim { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Account), new Claim(UserId, user.Id.ToString()) }; var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret)); var creds new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token new JwtSecurityToken( issuer: _issuer, audience: _audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(_expireMinutes), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } /// summary /// 校验Token /// /summary public bool ValidateToken(string token, out ClaimsPrincipal? claims) { claims null; try { var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret)); var validateParam new TokenValidationParameters { ValidateIssuer true, ValidateAudience true, ValidateLifetime true, ValidateIssuerSigningKey true, ValidIssuer _issuer, ValidAudience _audience, IssuerSigningKey key, ClockSkew TimeSpan.Zero }; var handler new JwtSecurityTokenHandler(); claims handler.ValidateToken(token, validateParam, out _); return true; } catch { return false; } } }3. appsettings.json 配置Jwt: { Secret: ABCDEFG123456789ABCDEFG123456789, Issuer: AdminWeb, Audience: AdminClient, ExpireMinutes: 120 }4. Program.cs 注册 JWT// 读取配置 var jwtSecret builder.Configuration[Jwt:Secret]; var jwtIssuer builder.Configuration[Jwt:Issuer]; var jwtAudience builder.Configuration[Jwt:Audience]; var jwtExpire int.Parse(builder.Configuration[Jwt:ExpireMinutes]!); // 注册 JWT 服务 builder.Services.AddSingleton(new JwtService(jwtSecret, jwtIssuer, jwtAudience, jwtExpire));六、登录接口完整实现你最关心Application/Services/UserService.csusing Admin.NET.Application.Dtos; using Admin.NET.Application.Vos; using Admin.NET.Infrastructure.Commons.Jwt; using Admin.NET.Infrastructure.Commons.Security; public async TaskRLoginVo LoginAsync(LoginDto dto) { // 1. 查询用户 var user await _uow.UserRepository.GetUserByAccountAsync(dto.Account); if (user null) return RLoginVo.Fail(账号或密码错误); // 2. 校验密码BCrypt if (!BCryptUtil.Verify(dto.Password, user.Password)) return RLoginVo.Fail(账号或密码错误); // 3. 生成 JWT Token var token _jwtService.GenerateToken(user); // 4. 组装返回 VO var vo new LoginVo { Token token, UserId user.Id, Account user.Account, Name user.Name }; return RLoginVo.Success(vo); }UserController 登录接口[HttpPost(login)] public async TaskActionResultRLoginVo Login([FromBody] LoginDto dto) { return await _userService.LoginAsync(dto); }七、添加用户时密码必须加密// 新增用户时 user.Password BCryptUtil.HashPassword(dto.Password);