# 身份认证 (08_Auth)

最后更新: 2024-09-20


# 📚 概述

DarkM 身份认证模块提供完整的身份认证和授权机制,支持 JWT、Basic、Digest 等多种认证方式,以及基于 RBAC 的权限控制。

源代码位置: DarkM/src/Framework/Auth


# 🏗️ 模块架构

# 完整目录结构

Auth/
├── Auth.Abstractions/
│   ├── ILoginHandler.cs                   # 登录处理器接口
│   ├── ILoginInfo.cs                      # 登录信息接口
│   ├── IPermissionValidateHandler.cs      # 权限验证处理器接口
│   ├── IDataScopeResolver.cs              # 数据范围解析器接口
│   ├── ClaimsName.cs                      # Claims 名称常量
│   ├── DataDimensionLevel.cs              # 数据维度级别枚举
│   ├── DataScopeModel.cs                  # 数据范围模型
│   └── LoginModels/
│       ├── LoginModel.cs                  # 登录模型
│       └── LoginResultModel.cs            # 登录结果模型
│
├── Auth.Jwt/
│   ├── JwtLoginHandler.cs                 # JWT 登录处理器
│   ├── JwtTokenModel.cs                   # JWT Token 模型
│   └── ServiceCollectionExtensions.cs     # DI 扩展
│
├── Auth.Basic/
│   ├── BasicAuthenticationHandler.cs      # Basic 认证处理器
│   └── ServiceCollectionExtensions.cs     # DI 扩展
│
├── Auth.Digest/
│   ├── DigestAuthenticationHandler.cs     # Digest 认证处理器
│   └── ServiceCollectionExtensions.cs     # DI 扩展
│
├── Auth.Third/
│   ├── Request/
│   │   ├── IAuthRequest.cs                # 认证请求接口
│   │   └── AuthRequests/                  # 各平台认证请求
│   ├── Config/
│   │   ├── IAuthSource.cs                 # 认证源接口
│   │   └── AuthSources/                   # 各平台认证源
│   └── Models/
│       ├── AuthResponse.cs                # 认证响应
│       ├── AuthUser.cs                    # 认证用户
│       └── AuthToken.cs                   # 认证 Token
│
└── Auth.Web/
    ├── ControllerAbstract.cs              # 控制器抽象基类
    ├── LoginInfo.cs                       # 登录信息实现
    ├── Attributes/
    │   ├── PermissionValidateAttribute.cs # 权限验证特性
    │   ├── CommonAttribute.cs             # 公共接口特性
    │   └── AllowAnonymousAttribute.cs     # 匿名访问特性
    └── PermissionDescriptor.cs            # 权限描述符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 🔧 支持的认证方式

认证方式 说明 适用场景
JWT JSON Web Token,无状态认证 前后端分离、移动端
Basic 基础认证,用户名密码 Base64 编码 内部系统、API 调用
Digest 摘要认证,密码不明文传输 安全性要求较高的场景
Third 第三方登录(微信、QQ、钉钉等) 社交登录、单点登录

# 💡 配置说明

# appsettings.json 配置

{
  "Auth": {
    // JWT 配置
    "Jwt": {
      // JWT 密钥(建议 32 位以上)
      "SecretKey": "your-secret-key-here-min-32-chars",
      // 颁发者
      "Issuer": "DarkM",
      // 受众
      "Audience": "DarkM.Client",
      // Token 有效期(分钟)
      "Expiration": 120,
      // 刷新 Token 有效期(分钟)
      "RefreshExpiration": 10080
    },
    
    // Basic 认证配置
    "Basic": {
      // 是否启用
      "Enabled": true,
      // Realm
      "Realm": "DarkM"
    },
    
    // Digest 认证配置
    "Digest": {
      "Enabled": true,
      "Realm": "DarkM"
    },
    
    // 第三方认证配置
    "Third": {
      "WeChat": {
        "Enabled": true,
        "AppId": "your-app-id",
        "AppSecret": "your-app-secret"
      },
      "DingTalk": {
        "Enabled": true,
        "AppKey": "your-app-key",
        "AppSecret": "your-app-secret"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 配置项详解

配置项 类型 默认值 说明
Jwt.SecretKey string - JWT 密钥(32 位以上)
Jwt.Issuer string DarkM Token 颁发者
Jwt.Audience string DarkM.Client Token 受众
Jwt.Expiration int 120 Token 有效期(分钟)
Jwt.RefreshExpiration int 10080 刷新 Token 有效期(分钟)
Basic.Enabled bool false 是否启用 Basic 认证
Digest.Enabled bool false 是否启用 Digest 认证

# 🔧 核心接口

# 1. ILoginHandler(登录处理器)

文件位置: Auth.Abstractions/ILoginHandler.cs

public interface ILoginHandler
{
    /// <summary>
    /// 生成 Token
    /// </summary>
    /// <param name="claims">用户声明</param>
    /// <returns>登录结果</returns>
    LoginResultModel Hand(IEnumerable<Claim> claims);
}
1
2
3
4
5
6
7
8
9

# 2. ILoginInfo(登录信息)

文件位置: Auth.Abstractions/ILoginInfo.cs

public interface ILoginInfo
{
    /// <summary>
    /// 账户 ID
    /// </summary>
    int AccountId { get; }
    
    /// <summary>
    /// 账户名称
    /// </summary>
    string AccountName { get; }
    
    /// <summary>
    /// 账户类型
    /// </summary>
    int AccountType { get; }
    
    /// <summary>
    /// 平台
    /// </summary>
    int Platform { get; }
    
    /// <summary>
    /// IP 地址
    /// </summary>
    string IPv4 { get; }
    
    /// <summary>
    /// 登录时间
    /// </summary>
    DateTime LoginTime { get; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 3. IPermissionValidateHandler(权限验证处理器)

文件位置: Auth.Abstractions/IPermissionValidateHandler.cs

public interface IPermissionValidateHandler
{
    /// <summary>
    /// 验证权限
    /// </summary>
    /// <param name="permissionCode">权限编码</param>
    /// <param name="accountId">账户 ID</param>
    /// <returns>是否有权限</returns>
    Task<bool> ValidateAsync(string permissionCode, int accountId);
}
1
2
3
4
5
6
7
8
9
10

# 4. IDataScopeResolver(数据范围解析器)

文件位置: Auth.Abstractions/IDataScopeResolver.cs

public interface IDataScopeResolver
{
    /// <summary>
    /// 解析数据范围
    /// </summary>
    /// <param name="accountId">账户 ID</param>
    /// <param name="entityType">实体类型</param>
    /// <returns>数据范围</returns>
    DataScopeModel Resolve(int accountId, string entityType);
}
1
2
3
4
5
6
7
8
9
10

# 5. ClaimsName 常量

文件位置: Auth.Abstractions/ClaimsName.cs

public static class ClaimsName
{
    /// <summary>
    /// 账户 ID
    /// </summary>
    public const string AccountId = "account_id";
    
    /// <summary>
    /// 账户名称
    /// </summary>
    public const string AccountName = "account_name";
    
    /// <summary>
    /// 账户类型
    /// </summary>
    public const string AccountType = "account_type";
    
    /// <summary>
    /// 平台
    /// </summary>
    public const string Platform = "platform";
    
    /// <summary>
    /// 角色 IDs
    /// </summary>
    public const string RoleIds = "role_ids";
    
    /// <summary>
    /// 权限 Codes
    /// </summary>
    public const string PermissionCodes = "permission_codes";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 💡 使用示例

# 1. JWT 认证登录

using DarkM.Lib.Auth.Abstractions;
using DarkM.Lib.Auth.Jwt;
using System.Security.Claims;

public class AccountService : IAccountService
{
    private readonly ILoginHandler _loginHandler;
    private readonly IAccountRepository _accountRepository;

    public AccountService(
        ILoginHandler loginHandler, 
        IAccountRepository accountRepository)
    {
        _loginHandler = loginHandler;
        _accountRepository = accountRepository;
    }

    public async Task<IResultModel> Login(LoginModel model)
    {
        // 验证用户名密码
        var account = await _accountRepository.GetByNameAsync(model.Username);
        if (account == null || !VerifyPassword(model.Password, account.Password))
            return ResultModel.Failed("用户名或密码错误");
        
        // 创建 Claims
        var claims = new[]
        {
            new Claim(ClaimsName.AccountId, account.Id.ToString()),
            new Claim(ClaimsName.AccountName, account.Name),
            new Claim(ClaimsName.AccountType, account.Type.ToString()),
            new Claim(ClaimsName.Platform, model.Platform.ToString())
        };
        
        // 生成 Token
        var result = _loginHandler.Hand(claims);
        
        return ResultModel.Success(result);
    }
    
    private bool VerifyPassword(string password, string hash)
    {
        // 使用 BCrypt 或其他算法验证密码
        return BCrypt.Net.BCrypt.Verify(password, hash);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 2. Token 结构

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
  "expiresIn": 7200,
  "tokenType": "Bearer"
}
1
2
3
4
5
6

# 3. 使用 Token

在请求头中添加 Token:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
1

# 4. 获取当前登录用户信息

using DarkM.Lib.Auth.Abstractions;

[Description("用户管理")]
public class UserController : ModuleController
{
    private readonly ILoginInfo _loginInfo;
    
    public UserController(ILoginInfo loginInfo)
    {
        _loginInfo = loginInfo;
    }
    
    [HttpGet]
    [Description("获取当前用户信息")]
    public async Task<IResultModel> GetCurrentUser()
    {
        var accountId = _loginInfo.AccountId;
        var accountName = _loginInfo.AccountName;
        
        var account = await _accountService.GetAsync(accountId);
        
        return ResultModel.Success(account);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 5. 数据权限过滤

public class OrderService : IOrderService
{
    private readonly ILoginInfo _loginInfo;
    private readonly IDataScopeResolver _dataScopeResolver;
    private readonly IOrderRepository _repository;
    
    public OrderService(
        ILoginInfo loginInfo,
        IDataScopeResolver dataScopeResolver,
        IOrderRepository repository)
    {
        _loginInfo = loginInfo;
        _dataScopeResolver = dataScopeResolver;
        _repository = repository;
    }
    
    public async Task<PageModel<OrderEntity>> QueryAsync(OrderQueryModel model)
    {
        // 获取数据范围
        var dataScope = _dataScopeResolver.Resolve(_loginInfo.AccountId, "Order");
        
        // 应用数据范围过滤
        var query = _repository.Find();
        
        switch (dataScope.Level)
        {
            case DataDimensionLevel.All:
                // 全部数据,不过滤
                break;
                
            case DataDimensionLevel.DeptWithChildren:
                // 本部门及子部门
                query = query.Where(e => dataScope.DeptIds.Contains(e.DeptId));
                break;
                
            case DataDimensionLevel.Dept:
                // 本部门
                query = query.Where(e => e.DeptId == _loginInfo.DeptId);
                break;
                
            case DataDimensionLevel.Self:
                // 仅本人
                query = query.Where(e => e.CreatorId == _loginInfo.AccountId);
                break;
                
            case DataDimensionLevel.Custom:
                // 自定义
                query = query.Where(e => dataScope.CustomIds.Contains(e.Id));
                break;
        }
        
        return await query.PageAsync(model.Page, model.PageSize);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 🔧 权限控制

# 权限验证特性

# PermissionValidate

控制器默认需要授权才能访问:

using DarkM.Lib.Auth.Web.Attributes;

[PermissionValidate]  // 需要权限验证
public abstract class ControllerAbstract : ControllerBase
{
    // ...
}
1
2
3
4
5
6
7

每个模块的 ModuleController 都继承自 ControllerAbstract,因此默认所有接口都需要授权。


# Common 特性

标记为 Common 的接口只需要登录即可访问:

using DarkM.Module.Common.Web.Attributes;

[HttpGet]
[Description("字典下拉列表")]
[Common]  // 只需要登录,不需要特定权限
public Task<IResultModel> Select(string group, string code)
{
    return _service.Select(group, code);
}
1
2
3
4
5
6
7
8
9

# AllowAnonymous

允许匿名访问:

[HttpPost]
[AllowAnonymous]  // 允许匿名访问
[Description("登录")]
public async Task<IResultModel> Login(LoginModel model)
{
    // ...
}
1
2
3
4
5
6
7

# 权限编码

权限编码格式:Area_Controller_Action_HttpMethod

接口 权限编码 说明
GET /api/config Admin_Config_Query_Get 查询配置项
POST /api/user Admin_User_Add_Post 添加用户
DELETE /api/role/{id} Admin_Role_Delete_Delete 删除角色
PUT /api/order/{id} Admin_Order_Update_Put 更新订单

# 数据维度级别

public enum DataDimensionLevel
{
    /// <summary>
    /// 全部数据
    /// </summary>
    All = 0,
    
    /// <summary>
    /// 本部门及子部门
    /// </summary>
    DeptWithChildren = 1,
    
    /// <summary>
    /// 本部门
    /// </summary>
    Dept = 2,
    
    /// <summary>
    /// 仅本人
    /// </summary>
    Self = 3,
    
    /// <summary>
    /// 自定义
    /// </summary>
    Custom = 4
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 📚 最佳实践

# 1. 使用 JWT 认证

对于前后端分离项目,推荐使用 JWT 认证。


# 2. Token 刷新机制

实现 Token 刷新机制,避免频繁登录:

public async Task<IResultModel> RefreshToken(string refreshToken)
{
    // 验证刷新 Token
    var isValid = await _tokenService.ValidateRefreshTokenAsync(refreshToken);
    if (!isValid)
        return ResultModel.Failed("刷新 Token 无效");
    
    // 生成新的 Token
    var newToken = await _tokenService.GenerateTokenAsync(refreshToken);
    
    return ResultModel.Success(newToken);
}
1
2
3
4
5
6
7
8
9
10
11
12

# 3. 密码加密

使用 BCrypt 或其他安全算法加密密码:

public string HashPassword(string password)
{
    return BCrypt.Net.BCrypt.HashPassword(password);
}

public bool VerifyPassword(string password, string hash)
{
    return BCrypt.Net.BCrypt.Verify(password, hash);
}
1
2
3
4
5
6
7
8
9

# 4. 权限缓存

缓存用户权限信息,提高性能:

public async Task<bool> ValidateAsync(string permissionCode, int accountId)
{
    var cacheKey = $"permissions:{accountId}";
    
    var permissions = await _cache.GetAsync<List<string>>(cacheKey);
    if (permissions == null)
    {
        permissions = await _permissionRepository.GetByAccountIdAsync(accountId);
        await _cache.SetAsync(cacheKey, permissions, TimeSpan.FromMinutes(30));
    }
    
    return permissions.Contains(permissionCode);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5. 审计日志

启用审计日志记录用户操作:

{
  "Admin": {
    "Auditing": true  // 是否开启审计
  }
}
1
2
3
4
5

禁用特定接口的审计:

[HttpPost]
[DisableAuditing]  // 禁用审计
[Description("登录")]
public async Task<IResultModel> Login(LoginModel model)
{
    // ...
}
1
2
3
4
5
6
7

# 🔍 常见问题

# Q1: Token 过期如何处理?

解决方案:

  1. 使用刷新 Token 获取新的 Token
  2. 重新登录
// Token 过期后,使用刷新 Token
public async Task<IResultModel> RefreshToken(string refreshToken)
{
    var newToken = await _tokenService.RefreshTokenAsync(refreshToken);
    return ResultModel.Success(newToken);
}
1
2
3
4
5
6

# Q2: 权限不生效?

检查清单:

  1. ✅ 是否正确配置了权限
  2. ✅ 用户是否被授权
  3. ✅ 权限编码是否正确
  4. ✅ 缓存是否已更新

解决方案:

// 清除权限缓存
await _cache.RemoveAsync($"permissions:{accountId}");
1
2

# Q3: 跨域认证问题?

解决方案:

确保在 CORS 配置中允许 Authorization 头:

services.AddCors(options =>
{
    options.AddPolicy("AllowAll", builder =>
    {
        builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader()
               .WithExposedHeaders("Authorization");
    });
});
1
2
3
4
5
6
7
8
9
10

# Q4: 如何实现第三方登录?

解决方案:

  1. 配置第三方认证源
  2. 使用 AuthRequestFactory 创建认证请求
  3. 处理回调获取用户信息
// 配置
{
  "Auth": {
    "Third": {
      "WeChat": {
        "Enabled": true,
        "AppId": "your-app-id",
        "AppSecret": "your-app-secret"
      }
    }
  }
}

// 使用
var authRequest = AuthRequestFactory.Create(AuthSource.WeChat);
var authorizationUrl = authRequest.Authorize("state");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Q5: 如何实现单点登录(SSO)?

解决方案:

  1. 使用统一的认证中心
  2. 使用 JWT Token 共享用户信息
  3. 实现 Token 验证接口
// 认证中心生成 Token
var token = _jwtService.GenerateToken(claims);

// 应用系统验证 Token
var claims = _jwtService.ValidateToken(token);
1
2
3
4
5

# 📚 相关文档


# 🔗 参考链接


最后更新: 2024-09-20