# 身份认证 (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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
# 5. 审计日志
启用审计日志记录用户操作:
{
"Admin": {
"Auditing": true // 是否开启审计
}
}
1
2
3
4
5
2
3
4
5
禁用特定接口的审计:
[HttpPost]
[DisableAuditing] // 禁用审计
[Description("登录")]
public async Task<IResultModel> Login(LoginModel model)
{
// ...
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 🔍 常见问题
# Q1: Token 过期如何处理?
解决方案:
- 使用刷新 Token 获取新的 Token
- 重新登录
// 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
2
3
4
5
6
# Q2: 权限不生效?
检查清单:
- ✅ 是否正确配置了权限
- ✅ 用户是否被授权
- ✅ 权限编码是否正确
- ✅ 缓存是否已更新
解决方案:
// 清除权限缓存
await _cache.RemoveAsync($"permissions:{accountId}");
1
2
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
2
3
4
5
6
7
8
9
10
# Q4: 如何实现第三方登录?
解决方案:
- 配置第三方认证源
- 使用 AuthRequestFactory 创建认证请求
- 处理回调获取用户信息
// 配置
{
"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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Q5: 如何实现单点登录(SSO)?
解决方案:
- 使用统一的认证中心
- 使用 JWT Token 共享用户信息
- 实现 Token 验证接口
// 认证中心生成 Token
var token = _jwtService.GenerateToken(claims);
// 应用系统验证 Token
var claims = _jwtService.ValidateToken(token);
1
2
3
4
5
2
3
4
5
# 📚 相关文档
# 🔗 参考链接
- 源代码 (opens new window) -
src/Framework/Auth - JWT 官方文档 (opens new window)
- BCrypt GitHub (opens new window)
最后更新: 2024-09-20