Cursor rule
.cursor/rules/security.mdcCodeSpirit 安全规范 - 权限系统、审计实体、多租户隔离、数据保护
Cursor rules
Quality
54/100
Scores the file, not the repository.Length
777 words
27 headings · 16 code blocksRepository
56
— · pushed 134 days agoLast changed
3 days ago
First indexed 3 days ago.1234567891011# 安全规范1213> 📖 控制器级别的安全特性(`[Authorize]`、`[AllowAnonymous]`、`[NoAudit]`、`[Audit]`)参见 [controller.mdc](mdc:.cursor/rules/controller.mdc)1415## 权限系统架构1617### 权限代码格式1819权限代码由系统自动生成,格式为:`{module}_{controller}_{action}`2021```22exam_questions_getList // 考试模块-题目控制器-获取列表23identity_users_create // 身份模块-用户控制器-创建24```2526### PermissionAttribute 特性2728用于自定义权限名称、描述和继承关系:2930```csharp31using CodeSpirit.Core.Attributes;3233[HttpPut("{id}")]34[Permission(35 Name = "exam_questions_update", // 自定义权限代码36 DisplayName = "更新题目", // 显示名称37 Description = "允许更新题目内容", // 权限描述38 Parent = "exam_questions", // 父级权限39 AllowInheritedPermissions = new[] { "exam_questions_manage" } // 继承权限40)]41[DisplayName("更新题目")]42public async Task<ActionResult<ApiResponse<QuestionDto>>> Update(43 long id, [FromBody] UpdateQuestionDto dto)44{45 // 拥有 exam_questions_manage 权限的用户也可以执行此操作46}47```4849### 权限继承机制5051`AllowInheritedPermissions` 允许配置权限继承,用户拥有任一继承权限时可访问当前接口:5253```csharp54// 方法级别继承55[Permission(AllowInheritedPermissions = new[] { "Question.Manage", "Question.Admin" })]56public async Task<ActionResult> UpdateQuestion(long id) { }57```5859### IHasPermissionService6061服务层权限检查:6263```csharp64public class QuestionService : IScopedDependency65{66 private readonly IHasPermissionService _permissionService;6768 public QuestionService(IHasPermissionService permissionService)69 {70 _permissionService = permissionService;71 }7273 public async Task<bool> CanManageQuestionAsync()74 {75 // 检查用户是否拥有指定权限76 return _permissionService.HasPermission("exam_questions_manage");77 }7879 public async Task<bool> CanAccessModuleAsync()80 {81 // 检查导航权限(仅检查一二级权限)82 return _permissionService.HasNavigationPermission("exam_questions");83 }84}85```8687## 审计实体8889### AuditableEntityBase9091实体继承 `AuditableEntityBase<TKey>` 自动记录审计信息:9293```csharp94using CodeSpirit.Shared.Entities;9596public class Question : AuditableEntityBase<long>, IMultiTenant97{98 // 自动记录以下字段(由框架自动填充):99 // - CreatedBy (long) 创建人ID100 // - CreatedAt (DateTime) 创建时间101 // - UpdatedBy (long?) 更新人ID102 // - UpdatedAt (DateTime?) 更新时间103 // - IsDeleted (bool) 软删除标记104 // - DeletedBy (long?) 删除人ID105 // - DeletedAt (DateTime?) 删除时间106107 public string Content { get; set; } = string.Empty;108 public string TenantId { get; set; } = string.Empty; // 多租户支持109}110```111112### 实体审计接口113114| 接口 | 说明 | 包含字段 |115|-----|------|---------|116| `ICreatable` | 创建审计 | `CreatedBy`, `CreatedAt` |117| `IUpdatable` | 更新审计 | `UpdatedBy`, `UpdatedAt` |118| `ISoftDelete` | 软删除 | `IsDeleted`, `DeletedBy`, `DeletedAt` |119| `IFullAuditable` | 完整审计 | 以上全部 |120121## 多租户数据隔离122123### IMultiTenant 接口124125实体实现 `IMultiTenant` 接口,自动应用租户数据过滤:126127```csharp128using CodeSpirit.Core;129130public class Question : AuditableEntityBase<long>, IMultiTenant131{132 public string TenantId { get; set; } = string.Empty; // 注意:类型为 string133 public string Content { get; set; } = string.Empty;134}135```136137### 租户过滤行为138139```csharp140// 查询时自动应用租户过滤器,无需手动添加 Where 条件141public async Task<List<QuestionDto>> GetListAsync()142{143 var entities = await _dbContext.Questions144 .ToListAsync(); // ✅ 自动过滤当前租户的数据145 return _mapper.Map<List<QuestionDto>>(entities);146}147148// ❌ 禁止:手动添加租户过滤(除非明确需要跨租户查询)149public async Task<List<QuestionDto>> GetListAsync()150{151 var entities = await _dbContext.Questions152 .Where(q => q.TenantId == _currentTenant.Id) // 不需要153 .ToListAsync();154}155```156157## 数据保护158159### 排除敏感字段160161DTO 中排除不应返回给客户端的字段:162163```csharp164using Newtonsoft.Json;165using CodeSpirit.Amis.Attributes.Columns;166167public class UserDto168{169 public long Id { get; set; }170 public string Username { get; set; } = string.Empty;171172 [JsonIgnore] // 不在 API 响应中返回173 public string PasswordHash { get; set; } = string.Empty;174175 [IgnoreColumn] // 不在列表表格中显示176 public string InternalNote { get; set; } = string.Empty;177}178```179180### 密码安全存储181182密码必须使用哈希存储,禁止明文:183184```csharp185// ✅ 正确:存储密码哈希186user.PasswordHash = _passwordHasher.HashPassword(user, password);187188// ❌ 禁止:明文存储密码189user.Password = password;190```191192## SQL 注入防护193194使用 EF Core 参数化查询(默认安全):195196```csharp197// ✅ 安全:EF Core 自动参数化198public async Task<Question?> GetByCodeAsync(string code)199{200 return await _dbContext.Questions201 .FirstOrDefaultAsync(q => q.Code == code);202}203204// ✅ 安全:使用参数化 SQL205public async Task<List<Question>> SearchAsync(string keyword)206{207 return await _dbContext.Questions208 .FromSqlInterpolated($"SELECT * FROM Questions WHERE Content LIKE {$"%{keyword}%"}")209 .ToListAsync();210}211212// ❌ 危险:字符串拼接(禁止)213public async Task<Question?> GetByCodeAsync(string code)214{215 var sql = $"SELECT * FROM Questions WHERE Code = '{code}'"; // SQL 注入风险!216 return await _dbContext.Questions.FromSqlRaw(sql).FirstOrDefaultAsync();217}218```219220## 日志安全221222### 敏感信息脱敏223224日志中禁止记录敏感信息:225226```csharp227// ✅ 安全:只记录必要信息228_logger.LogInformation("用户登录: {Username}, IP: {IpAddress}",229 username,230 ipAddress);231232// ✅ 安全:使用脱敏字符串233_logger.LogInformation("API密钥验证: {MaskedKey}",234 $"{apiKey[..4]}****{apiKey[^4..]}");235236// ❌ 危险:记录密码(禁止)237_logger.LogInformation("用户登录: {Username}, Password: {Password}",238 username, password);239240// ❌ 危险:记录完整 Token(禁止)241_logger.LogInformation("Token: {Token}", token);242```243244### 异常日志处理245246```csharp247try248{249 await _service.ProcessAsync(data);250}251catch (Exception ex)252{253 // ✅ 记录异常但不暴露敏感上下文254 _logger.LogError(ex, "处理数据失败: EntityId={EntityId}", data.Id);255 throw; // 重新抛出,由统一异常处理器处理256}257```258259## CORS 配置260261框架统一配置 CORS,API 项目无需单独配置:262263```csharp264// CommonApiServiceExtensions 已配置265app.UseCors("AllowSpecificOriginsWithCredentials");266```267268自定义配置(仅在特殊需求时):269270```csharp271// appsettings.json272{273 "Cors": {274 "AllowedOrigins": ["https://yourdomain.com"],275 "AllowCredentials": true276 }277}278```279280## CSRF/XSS 防护281282- **CSRF**: API 使用 JWT 认证,天然防御 CSRF 攻击283- **XSS**: AMIS 框架自动转义输出,后端无需手动处理284285## 安全配置286287### JWT 配置288289```json290{291 "Jwt": {292 "SecretKey": "your-secret-key-at-least-32-characters",293 "Issuer": "CodeSpirit",294 "Audience": "CodeSpirit",295 "ExpireMinutes": 120,296 "RefreshExpireMinutes": 10080297 }298}299```300301### 审计配置302303```json304{305 "Audit": {306 "Enabled": true,307 "LogRequestParams": true,308 "LogResponseData": false,309 "SensitiveData": {310 "Enabled": true,311 "SensitiveFieldPatterns": ["password", "token", "apiKey", "secret"],312 "ExcludedFields": ["password", "newPassword", "confirmPassword"]313 }314 }315}316```317318## 禁止事项319320| 禁止 | 原因 | 正确做法 |321|-----|------|---------|322| 明文存储密码 | 安全风险 | 使用密码哈希 |323| SQL 字符串拼接 | SQL 注入风险 | EF Core 参数化查询 |324| 日志记录密码/Token | 信息泄露 | 只记录必要标识信息 |325| 手动租户过滤 | 可能遗漏 | 使用 `IMultiTenant` 自动过滤 |326| DTO 返回密码字段 | 信息泄露 | 使用 `[JsonIgnore]` 排除 |327328## 安全检查清单329330- [ ] 实体继承 `AuditableEntityBase` 实现自动审计331- [ ] 多租户实体实现 `IMultiTenant` 接口332- [ ] DTO 中使用 `[JsonIgnore]` 排除敏感字段333- [ ] 密码使用哈希存储334- [ ] 数据库查询使用 EF Core 参数化(避免 `FromSqlRaw` 拼接)335- [ ] 日志中不记录密码、Token 等敏感信息336- [ ] 敏感操作配置权限检查337- [ ] 定期审查权限配置和审计日志338339## 参考文件340341- 权限特性: [PermissionAttribute.cs](mdc:Src/CodeSpirit.Core/Attributes/PermissionAttribute.cs)342- 权限服务: [IHasPermissionService.cs](mdc:Src/CodeSpirit.Core/Authorization/IHasPermissionService.cs)343- 审计实体基类: [AuditableEntityBase.cs](mdc:Src/CodeSpirit.Shared/Entities/AuditableEntityBase.cs)344- 多租户接口: [IMultiTenant.cs](mdc:Src/CodeSpirit.Core/IMultiTenant.cs)345- 审计组件: [CodeSpirit.Audit/README.md](mdc:Src/Components/CodeSpirit.Audit/README.md)346- 控制器规范: [controller.mdc](mdc:.cursor/rules/controller.mdc)347
Also in xin-lai/CodeSpirit
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| xin-lai/CodeSpirit.cursor/rules/js.mdc · 56 | Cursor rules | apidocs | 46/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/ai-development.mdc · 56 | Cursor rules | api | 46/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/all.mdc · 56 | Cursor rules | testing-strategyapi | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/amis-cards.mdc · 56 | Cursor rules | no sections | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/api-design.mdc · 56 | Cursor rules | no sections | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/controller.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/cs.mdc · 56 | Cursor rules | no sections | 25/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/csproj.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/css.mdc · 56 | Cursor rules | ui | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/database.mdc · 56 | Cursor rules | no sections | 74/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/dependency-injection.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/dto.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/enum.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/i18n.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/naming-conventions.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/package-management.mdc · 56 | Cursor rules | no sections | 74/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/performance.mdc · 56 | Cursor rules | no sections | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/project-structure.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/service.mdc · 56 | Cursor rules | no sections | 50/100 | 3 days ago | |
| xin-lai/CodeSpirit.cursor/rules/startup-framework.mdc · 56 | Cursor rules | api | 54/100 | 3 days ago |
Diff against .cursor/rules/js.mdc Diff against .cursor/rules/ai-development.mdc Diff against .cursor/rules/all.mdc Diff against .cursor/rules/amis-cards.mdc Diff against .cursor/rules/api-design.mdc Diff against .cursor/rules/controller.mdc Diff against .cursor/rules/cs.mdc Diff against .cursor/rules/csproj.mdc Diff against .cursor/rules/css.mdc Diff against .cursor/rules/database.mdc Diff against .cursor/rules/dependency-injection.mdc Diff against .cursor/rules/dto.mdc Diff against .cursor/rules/enum.mdc Diff against .cursor/rules/i18n.mdc Diff against .cursor/rules/naming-conventions.mdc Diff against .cursor/rules/package-management.mdc Diff against .cursor/rules/performance.mdc Diff against .cursor/rules/project-structure.mdc Diff against .cursor/rules/service.mdc Diff against .cursor/rules/startup-framework.mdc
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 3 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago |
