Day23 登录 + 颁发 Token(DDD 四层架构 + 企业标准)

一、今日核心内容

  1. 密码加密(BCrypt) ------ 企业绝对标准,不能明文存密码
  2. 登录接口逻辑
  3. 登录成功 → 颁发 JWT Token
  4. 返回前端:Token + 用户信息(VO)
  5. 全部写在 DDD 四层架构

二、高频面试题 + 答案

1. 密码为什么不能用 SHA256?要用什么?

答案 :SHA256 是摘要算法,速度太快,容易被暴力破解。企业必须用 BCrypt / Argon2(自适应哈希,慢、加盐、防暴力破解)。

2. BCrypt 需要自己维护盐值吗?

答案 :不需要,BCrypt 自动生成盐、自动存在哈希里,验证时自动提取。

3. 登录流程是什么?

  1. 接收账号密码
  2. 查询用户
  3. BCrypt 验证密码
  4. 生成 JWT Token
  5. 返回 Token

4. JWT 存什么?

非敏感信息 :UserId、Account、RoleIds、RoleCodes绝对不存密码


三、项目结构(严格 DDD 四层)

复制代码
API               # 接口、Controller
Application       # 用例、Service、DTO、VO
Domain            # 实体、仓储接口
Infrastructure    # 技术实现:JWT、BCrypt、仓储实现、EF

今天所有代码都放在这 2 个位置:

1. 工具类(BCrypt + JWT)

复制代码
Infrastructure/
  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.cs

cs 复制代码
using 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.cs

cs 复制代码
using 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 List<Claim>
        {
            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 配置

cs 复制代码
"Jwt": {
  "Secret": "ABCDEFG123456789ABCDEFG123456789",
  "Issuer": "AdminWeb",
  "Audience": "AdminClient",
  "ExpireMinutes": 120
}

4. Program.cs 注册 JWT

cs 复制代码
// 读取配置
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.cs

cs 复制代码
using Admin.NET.Application.Dtos;
using Admin.NET.Application.Vos;
using Admin.NET.Infrastructure.Commons.Jwt;
using Admin.NET.Infrastructure.Commons.Security;

public async Task<R<LoginVo>> LoginAsync(LoginDto dto)
{
    // 1. 查询用户
    var user = await _uow.UserRepository.GetUserByAccountAsync(dto.Account);
    if (user == null)
        return R<LoginVo>.Fail("账号或密码错误");

    // 2. 校验密码(BCrypt)
    if (!BCryptUtil.Verify(dto.Password, user.Password))
        return R<LoginVo>.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 R<LoginVo>.Success(vo);
}

UserController 登录接口

cs 复制代码
[HttpPost("login")]
public async Task<ActionResult<R<LoginVo>>> Login([FromBody] LoginDto dto)
{
    return await _userService.LoginAsync(dto);
}

七、添加用户时密码必须加密

cs 复制代码
// 新增用户时
user.Password = BCryptUtil.HashPassword(dto.Password);
相关推荐
wangl_921 天前
C#性能优化完全指南 - 从原理到实践
开发语言·性能优化·c#·.net·.netcore·visual studio
宝桥南山5 天前
GitHub Models - 尝试一下使用GitHub Models
microsoft·ai·微软·c#·github·.netcore
武藤一雄5 天前
WPF进阶:万字详解WPF如何性能优化
windows·性能优化·c#·.net·wpf·.netcore·鲁棒性
van久9 天前
Day19:Service 业务层(企业架构核心)
.netcore
武藤一雄9 天前
WPF中逻辑树(Logical Tree)与可视化树(Visual Tree)到底是什么
microsoft·c#·.net·wpf·.netcore
武藤一雄13 天前
19个核心算法(C#版)
数据结构·windows·算法·c#·排序算法·.net·.netcore
van久14 天前
Day17:EF Core 增删改 + 事务
.netcore
MoFe115 天前
【.net core】【watercloud】处理rabbitmq类初始化时获取系统已注入的数据库连接问题(调用已注入服务)
数据库·rabbitmq·.netcore
MoFe119 天前
【.net core】【RabbitMq】rabbitmq在.net core中的简单使用
分布式·rabbitmq·.netcore