ai-engineering-init 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/skill-forced-eval.js +2 -0
- package/.claude/settings.json +3 -3
- package/.claude/skills/add-skill/SKILL.md +79 -32
- package/.claude/skills/api-development/SKILL.md +83 -377
- package/.claude/skills/architecture-design/SKILL.md +138 -632
- package/.claude/skills/backend-annotations/SKILL.md +134 -506
- package/.claude/skills/banana-image/SKILL.md +10 -3
- package/.claude/skills/brainstorm/SKILL.md +103 -535
- package/.claude/skills/bug-detective/SKILL.md +147 -1097
- package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
- package/.claude/skills/code-patterns/SKILL.md +116 -426
- package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.claude/skills/crud-development/SKILL.md +64 -304
- package/.claude/skills/data-permission/SKILL.md +105 -412
- package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.claude/skills/file-oss-management/SKILL.md +106 -714
- package/.claude/skills/file-oss-management/references/entities.md +105 -0
- package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
- package/.claude/skills/leniu-api-development/SKILL.md +142 -626
- package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
- package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
- package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
- package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
- package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
- package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
- package/.claude/skills/leniu-java-export/SKILL.md +94 -379
- package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
- package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +111 -365
- package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
- package/.claude/skills/mysql-debug/SKILL.md +364 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
- package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
- package/.claude/skills/openspec-explore/SKILL.md +10 -1
- package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
- package/.claude/skills/openspec-new-change/SKILL.md +9 -1
- package/.claude/skills/openspec-onboard/SKILL.md +15 -130
- package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
- package/.claude/skills/performance-doctor/SKILL.md +110 -434
- package/.claude/skills/redis-cache/SKILL.md +89 -595
- package/.claude/skills/redis-cache/references/listeners.md +23 -0
- package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
- package/.claude/skills/security-guard/SKILL.md +137 -532
- package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
- package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.claude/skills/sms-mail/SKILL.md +116 -574
- package/.claude/skills/sms-mail/references/mail-config.md +88 -0
- package/.claude/skills/sms-mail/references/sms-config.md +74 -0
- package/.claude/skills/social-login/SKILL.md +112 -514
- package/.claude/skills/social-login/references/provider-configs.md +118 -0
- package/.claude/skills/tenant-management/SKILL.md +129 -444
- package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.claude/skills/test-development/SKILL.md +86 -540
- package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
- package/.claude/skills/utils-toolkit/SKILL.md +52 -305
- package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.claude/skills/websocket-sse/SKILL.md +105 -550
- package/.claude/skills/workflow-engine/SKILL.md +147 -502
- package/.codex/skills/add-skill/SKILL.md +79 -32
- package/.codex/skills/api-development/SKILL.md +172 -599
- package/.codex/skills/architecture-design/SKILL.md +138 -504
- package/.codex/skills/backend-annotations/SKILL.md +134 -496
- package/.codex/skills/banana-image/SKILL.md +10 -3
- package/.codex/skills/brainstorm/SKILL.md +103 -535
- package/.codex/skills/bug-detective/SKILL.md +147 -1097
- package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
- package/.codex/skills/code-patterns/SKILL.md +120 -282
- package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.codex/skills/crud-development/SKILL.md +64 -292
- package/.codex/skills/data-permission/SKILL.md +108 -407
- package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.codex/skills/database-ops/SKILL.md +8 -154
- package/.codex/skills/error-handler/SKILL.md +10 -0
- package/.codex/skills/file-oss-management/SKILL.md +106 -714
- package/.codex/skills/file-oss-management/references/entities.md +105 -0
- package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
- package/.codex/skills/git-workflow/SKILL.md +27 -5
- package/.codex/skills/leniu-api-development/SKILL.md +142 -626
- package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
- package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
- package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
- package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
- package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
- package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
- package/.codex/skills/leniu-java-export/SKILL.md +94 -379
- package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
- package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +111 -365
- package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
- package/.codex/skills/mysql-debug/SKILL.md +364 -0
- package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
- package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
- package/.codex/skills/openspec-explore/SKILL.md +10 -1
- package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
- package/.codex/skills/openspec-new-change/SKILL.md +9 -1
- package/.codex/skills/openspec-onboard/SKILL.md +15 -130
- package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
- package/.codex/skills/performance-doctor/SKILL.md +110 -434
- package/.codex/skills/project-navigator/SKILL.md +20 -1
- package/.codex/skills/redis-cache/SKILL.md +93 -589
- package/.codex/skills/redis-cache/references/listeners.md +23 -0
- package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
- package/.codex/skills/security-guard/SKILL.md +141 -527
- package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
- package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.codex/skills/sms-mail/SKILL.md +116 -574
- package/.codex/skills/sms-mail/references/mail-config.md +88 -0
- package/.codex/skills/sms-mail/references/sms-config.md +74 -0
- package/.codex/skills/social-login/SKILL.md +112 -514
- package/.codex/skills/social-login/references/provider-configs.md +118 -0
- package/.codex/skills/store-pc/SKILL.md +258 -383
- package/.codex/skills/tenant-management/SKILL.md +129 -444
- package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.codex/skills/test-development/SKILL.md +86 -540
- package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
- package/.codex/skills/ui-pc/SKILL.md +350 -387
- package/.codex/skills/utils-toolkit/SKILL.md +52 -283
- package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.codex/skills/websocket-sse/SKILL.md +105 -550
- package/.codex/skills/workflow-engine/SKILL.md +147 -502
- package/.cursor/hooks.json +3 -3
- package/.cursor/skills/add-skill/SKILL.md +79 -32
- package/.cursor/skills/api-development/SKILL.md +83 -377
- package/.cursor/skills/architecture-design/SKILL.md +138 -632
- package/.cursor/skills/backend-annotations/SKILL.md +134 -506
- package/.cursor/skills/banana-image/SKILL.md +10 -3
- package/.cursor/skills/brainstorm/SKILL.md +103 -535
- package/.cursor/skills/bug-detective/SKILL.md +147 -1097
- package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
- package/.cursor/skills/code-patterns/SKILL.md +116 -426
- package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.cursor/skills/crud-development/SKILL.md +64 -304
- package/.cursor/skills/data-permission/SKILL.md +105 -412
- package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.cursor/skills/file-oss-management/SKILL.md +106 -714
- package/.cursor/skills/file-oss-management/references/entities.md +105 -0
- package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
- package/.cursor/skills/git-workflow/SKILL.md +27 -5
- package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
- package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
- package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
- package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
- package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
- package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
- package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
- package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
- package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
- package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.cursor/skills/leniu-report-customization/SKILL.md +111 -365
- package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
- package/.cursor/skills/mysql-debug/SKILL.md +364 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
- package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-explore/SKILL.md +10 -1
- package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
- package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
- package/.cursor/skills/performance-doctor/SKILL.md +110 -434
- package/.cursor/skills/redis-cache/SKILL.md +89 -595
- package/.cursor/skills/redis-cache/references/listeners.md +23 -0
- package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
- package/.cursor/skills/security-guard/SKILL.md +137 -532
- package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
- package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.cursor/skills/sms-mail/SKILL.md +116 -574
- package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
- package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
- package/.cursor/skills/social-login/SKILL.md +112 -514
- package/.cursor/skills/social-login/references/provider-configs.md +118 -0
- package/.cursor/skills/tenant-management/SKILL.md +129 -444
- package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.cursor/skills/test-development/SKILL.md +86 -540
- package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
- package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
- package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.cursor/skills/websocket-sse/SKILL.md +105 -550
- package/.cursor/skills/workflow-engine/SKILL.md +147 -502
- package/package.json +1 -1
|
@@ -1,416 +1,261 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: leniu-security-guard
|
|
3
3
|
description: |
|
|
4
|
-
leniu-tengyun-core / leniu-yunshitang
|
|
4
|
+
leniu-tengyun-core / leniu-yunshitang 项目安全权限控制规范。包含认证注解体系、TokenManager API、数据权限校验、SQL注入防护。
|
|
5
5
|
|
|
6
6
|
触发场景:
|
|
7
|
-
- 配置接口认证注解(@RequiresAuthentication/@RequiresGuest)
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- 配置接口认证注解(@RequiresAuthentication/@RequiresGuest/@RequiresPermissions)
|
|
8
|
+
- 使用 TokenManager 获取用户信息或校验权限
|
|
9
|
+
- 防 SQL 注入(MyBatis #{} 参数化查询)
|
|
10
|
+
- 数据归属校验(防越权)
|
|
11
|
+
- VO 敏感字段脱敏处理
|
|
12
12
|
|
|
13
13
|
适用项目:
|
|
14
14
|
- leniu-tengyun-core:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core
|
|
15
15
|
- leniu-yunshitang:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
触发词:安全认证、权限注解、TokenManager、SQL注入防护、数据脱敏、接口安全、RequiresAuthentication
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
# leniu-security-guard
|
|
21
21
|
|
|
22
|
-
适用于 leniu-tengyun-core / leniu-yunshitang 项目的安全权限控制。
|
|
23
|
-
|
|
24
22
|
## 认证注解
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|------|------|------|
|
|
28
|
-
| `@RequiresAuthentication` | 需要登录认证 | `net.xnzn.framework.secure.filter.annotation.RequiresAuthentication` |
|
|
29
|
-
| `@RequiresGuest` | 允许游客访问 | `net.xnzn.framework.secure.filter.annotation.RequiresGuest` |
|
|
30
|
-
| `@RequiresPermissions` | 需要指定权限 | `net.xnzn.framework.secure.filter.annotation.RequiresPermissions` |
|
|
31
|
-
| `@RequiresRoles` | 需要指定角色 | `net.xnzn.framework.secure.filter.annotation.RequiresRoles` |
|
|
32
|
-
| `@RequiresUser` | 需要用户登录 | `net.xnzn.framework.secure.filter.annotation.RequiresUser` |
|
|
33
|
-
| `@RequiresHeader` | 需要指定请求头 | `net.xnzn.framework.secure.filter.annotation.RequiresHeader` |
|
|
24
|
+
所有注解包路径:`net.xnzn.framework.secure.filter.annotation.*`
|
|
34
25
|
|
|
35
|
-
|
|
26
|
+
| 注解 | 用途 |
|
|
27
|
+
|------|------|
|
|
28
|
+
| `@RequiresAuthentication` | 需要登录认证(最常用,类/方法级) |
|
|
29
|
+
| `@RequiresGuest` | 允许游客访问(公开接口) |
|
|
30
|
+
| `@RequiresPermissions` | 需要指定权限码 |
|
|
31
|
+
| `@RequiresRoles` | 需要指定角色 |
|
|
32
|
+
| `@RequiresUser` | 需要用户登录 |
|
|
33
|
+
| `@RequiresHeader` | 需要指定请求头 |
|
|
36
34
|
|
|
37
|
-
### Controller
|
|
35
|
+
### Controller 认证示例
|
|
38
36
|
|
|
39
37
|
```java
|
|
40
38
|
import net.xnzn.framework.secure.filter.annotation.RequiresAuthentication;
|
|
41
39
|
import net.xnzn.framework.secure.filter.annotation.RequiresGuest;
|
|
40
|
+
import net.xnzn.framework.secure.filter.annotation.RequiresPermissions;
|
|
41
|
+
import net.xnzn.framework.secure.filter.annotation.RequiresRoles;
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// 类级别:整个 Controller 需要登录
|
|
44
44
|
@RestController
|
|
45
|
-
@RequestMapping("/api/v2/xxx")
|
|
45
|
+
@RequestMapping("/api/v2/web/xxx")
|
|
46
46
|
@RequiresAuthentication
|
|
47
|
-
public class
|
|
48
|
-
// ...
|
|
49
|
-
}
|
|
47
|
+
public class XxxWebController { }
|
|
50
48
|
|
|
51
|
-
//
|
|
49
|
+
// 方法级别:允许游客访问
|
|
52
50
|
@GetMapping("/public/info")
|
|
53
51
|
@RequiresGuest
|
|
54
|
-
public LeResponse<String> getPublicInfo() {
|
|
55
|
-
return LeResponse.succ("public data");
|
|
56
|
-
}
|
|
52
|
+
public LeResponse<String> getPublicInfo() { }
|
|
57
53
|
|
|
58
54
|
// 需要特定权限
|
|
59
55
|
@GetMapping("/admin/users")
|
|
60
56
|
@RequiresPermissions("system:user:list")
|
|
61
|
-
public LeResponse<List<User>> listUsers() {
|
|
62
|
-
return LeResponse.succ(userService.list());
|
|
63
|
-
}
|
|
57
|
+
public LeResponse<List<User>> listUsers() { }
|
|
64
58
|
|
|
65
|
-
//
|
|
59
|
+
// AND 逻辑(默认):需要所有权限
|
|
66
60
|
@RequiresPermissions(value = {"system:user:add", "system:user:edit"}, logical = Logical.AND)
|
|
67
|
-
public LeResponse<Void> addUser(User user) { }
|
|
68
61
|
|
|
69
|
-
//
|
|
62
|
+
// OR 逻辑:需要任一权限
|
|
70
63
|
@RequiresPermissions(value = {"system:user:add", "system:user:edit"}, logical = Logical.OR)
|
|
71
|
-
public LeResponse<Void> editUser(User user) { }
|
|
72
64
|
|
|
73
65
|
// 需要指定角色
|
|
74
66
|
@RequiresRoles("admin")
|
|
75
|
-
public LeResponse<Void> adminAction() { }
|
|
76
67
|
|
|
77
68
|
// 需要所有角色
|
|
78
69
|
@RequiresRoles(value = {"admin", "manager"}, logical = Logical.AND)
|
|
79
|
-
public LeResponse<Void> superAction() { }
|
|
80
70
|
```
|
|
81
71
|
|
|
82
|
-
|
|
72
|
+
> **注意**:`@RequiresPermissions` 不指定 `value` 时,自动使用 `@RequestMapping` 路径作为权限码。
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## TokenManager API
|
|
83
77
|
|
|
84
78
|
```java
|
|
85
79
|
import net.xnzn.framework.secure.token.TokenManager;
|
|
80
|
+
```
|
|
86
81
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
### 用户信息
|
|
83
|
+
|
|
84
|
+
```java
|
|
85
|
+
// 登录状态
|
|
86
|
+
TokenManager.isLogin();
|
|
91
87
|
|
|
92
|
-
//
|
|
88
|
+
// 用户 ID
|
|
93
89
|
Long userId = TokenManager.getSubjectId().orElse(null);
|
|
94
90
|
|
|
95
|
-
//
|
|
91
|
+
// 用户名
|
|
96
92
|
String userName = TokenManager.getSubjectName().orElse(null);
|
|
97
93
|
|
|
98
|
-
//
|
|
94
|
+
// 附加数据
|
|
99
95
|
Map<String, String> userData = TokenManager.getSubjectData();
|
|
100
96
|
String orgId = userData.get("orgId");
|
|
97
|
+
```
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
if (TokenManager.hasPermission("system:user:add")) {
|
|
104
|
-
// 有权限
|
|
105
|
-
}
|
|
99
|
+
### 权限/角色校验
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
101
|
+
```java
|
|
102
|
+
// 单个权限
|
|
103
|
+
TokenManager.hasPermission("system:user:add");
|
|
111
104
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
// 拥有任一权限
|
|
115
|
-
}
|
|
105
|
+
// 多个权限(AND)
|
|
106
|
+
TokenManager.hasPermission("system:user:add", "system:user:edit");
|
|
116
107
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
// 是管理员
|
|
120
|
-
}
|
|
108
|
+
// 任一权限(OR)
|
|
109
|
+
TokenManager.hasAnyPermission("system:user:add", "system:user:edit");
|
|
121
110
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
111
|
+
// 角色
|
|
112
|
+
TokenManager.hasRole("admin");
|
|
113
|
+
TokenManager.hasAnyRole("admin", "manager");
|
|
126
114
|
```
|
|
127
115
|
|
|
128
|
-
###
|
|
116
|
+
### 附加数据管理
|
|
129
117
|
|
|
130
118
|
```java
|
|
131
|
-
//
|
|
119
|
+
// 添加
|
|
132
120
|
TokenManager.attachData("orgId", "12345");
|
|
133
|
-
TokenManager.attachData("deptId", "67890");
|
|
134
121
|
|
|
135
122
|
// 批量添加
|
|
136
|
-
Map
|
|
137
|
-
data.put("orgId", "12345");
|
|
138
|
-
data.put("deptId", "67890");
|
|
139
|
-
TokenManager.attachData(data);
|
|
123
|
+
TokenManager.attachData(Map.of("orgId", "12345", "deptId", "67890"));
|
|
140
124
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
String orgId = userData.get("orgId");
|
|
125
|
+
// 获取
|
|
126
|
+
String orgId = TokenManager.getSubjectData().get("orgId");
|
|
144
127
|
|
|
145
|
-
//
|
|
128
|
+
// 移除
|
|
146
129
|
TokenManager.removeData("orgId", "deptId");
|
|
147
130
|
```
|
|
148
131
|
|
|
149
|
-
###
|
|
132
|
+
### 登出与缓存
|
|
150
133
|
|
|
151
134
|
```java
|
|
152
|
-
//
|
|
135
|
+
// 登出
|
|
153
136
|
TokenManager.logout();
|
|
154
137
|
|
|
155
|
-
//
|
|
138
|
+
// 强制下线
|
|
156
139
|
TokenManager.revokeAuthenticate();
|
|
157
140
|
|
|
158
|
-
//
|
|
141
|
+
// 撤销旧 Token(保留最近 N 个)
|
|
159
142
|
TokenManager.revokeAuthenticate(userId, 3);
|
|
160
|
-
```
|
|
161
143
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
```java
|
|
165
|
-
// 清除用户权限缓存
|
|
144
|
+
// 清除权限/角色缓存
|
|
166
145
|
TokenManager.clearPermission(userId);
|
|
167
|
-
|
|
168
|
-
// 清除用户角色缓存
|
|
169
146
|
TokenManager.clearRole(userId);
|
|
170
|
-
|
|
171
|
-
// 清除用户权限和角色缓存
|
|
172
147
|
TokenManager.clearRoleAndPermission(userId);
|
|
173
|
-
|
|
174
|
-
// 清除所有权限缓存
|
|
175
148
|
TokenManager.clearAllRoleAndPermission();
|
|
176
149
|
```
|
|
177
150
|
|
|
178
|
-
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## WebContext
|
|
179
154
|
|
|
180
155
|
```java
|
|
181
156
|
import net.xnzn.framework.secure.WebContext;
|
|
182
157
|
|
|
183
|
-
// 获取当前请求
|
|
184
158
|
HttpServletRequest request = WebContext.get().getRequest().orElse(null);
|
|
185
|
-
|
|
186
|
-
// 获取当前响应
|
|
187
159
|
HttpServletResponse response = WebContext.get().getResponse().orElse(null);
|
|
188
|
-
|
|
189
|
-
// 获取 AccessToken
|
|
190
160
|
Optional<AccessToken> token = WebContext.get().getAccessToken();
|
|
191
161
|
|
|
192
|
-
//
|
|
193
|
-
WebContext.get().setAttribute("
|
|
194
|
-
Object value = WebContext.get().getAttribute("
|
|
195
|
-
|
|
196
|
-
// 清除上下文
|
|
162
|
+
// 属性存取
|
|
163
|
+
WebContext.get().setAttribute("key", "value");
|
|
164
|
+
Object value = WebContext.get().getAttribute("key");
|
|
197
165
|
WebContext.reset();
|
|
198
166
|
```
|
|
199
167
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
### 使用参数化查询(MyBatis #{} 占位符)
|
|
203
|
-
|
|
204
|
-
```java
|
|
205
|
-
// ❌ 错误:字符串拼接有注入风险
|
|
206
|
-
String sql = "SELECT * FROM user WHERE name = '" + name + "'";
|
|
207
|
-
|
|
208
|
-
// ✅ 正确:使用 MyBatis 参数化查询
|
|
209
|
-
mapper.selectByName(name); // MyBatis 内部使用 #{name}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### MyBatis XML 中必须使用 `#{}`
|
|
213
|
-
|
|
214
|
-
```xml
|
|
215
|
-
<!-- ✅ 正确:使用 #{} -->
|
|
216
|
-
<select id="selectByName" resultType="User">
|
|
217
|
-
SELECT id, name, mobile FROM user WHERE name = #{name}
|
|
218
|
-
</select>
|
|
219
|
-
|
|
220
|
-
<!-- ❌ 错误:使用 ${} 有 SQL 注入风险 -->
|
|
221
|
-
<select id="selectByName" resultType="User">
|
|
222
|
-
SELECT * FROM user WHERE name = '${name}'
|
|
223
|
-
</select>
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### 禁止 SELECT *
|
|
227
|
-
|
|
228
|
-
```xml
|
|
229
|
-
<!-- ❌ 错误:使用 SELECT * -->
|
|
230
|
-
<select id="queryList">
|
|
231
|
-
SELECT * FROM user WHERE status = #{status}
|
|
232
|
-
</select>
|
|
233
|
-
|
|
234
|
-
<!-- ✅ 正确:明确指定字段 -->
|
|
235
|
-
<select id="queryList">
|
|
236
|
-
SELECT id, name, mobile, status FROM user WHERE status = #{status}
|
|
237
|
-
</select>
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## XSS 防护
|
|
241
|
-
|
|
242
|
-
### 输出时转义
|
|
243
|
-
|
|
244
|
-
```java
|
|
245
|
-
// 使用 Spring 的 HtmlUtils 转义
|
|
246
|
-
String safeContent = HtmlUtils.htmlEscape(userInput);
|
|
247
|
-
|
|
248
|
-
// 或使用 Apache Commons Text
|
|
249
|
-
String safeContent = StringEscapeUtils.escapeHtml4(userInput);
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### VO 中使用 @JsonSerialize
|
|
253
|
-
|
|
254
|
-
```java
|
|
255
|
-
@Data
|
|
256
|
-
public class ArticleVO {
|
|
257
|
-
@JsonSerialize(using = HtmlEscapeSerializer.class)
|
|
258
|
-
private String content; // 自动转义 HTML
|
|
259
|
-
}
|
|
260
|
-
```
|
|
168
|
+
---
|
|
261
169
|
|
|
262
170
|
## 数据权限校验(防越权)
|
|
263
171
|
|
|
264
172
|
```java
|
|
265
173
|
@Transactional(rollbackFor = Exception.class)
|
|
266
174
|
public void delete(Long id) {
|
|
267
|
-
// 查询数据
|
|
268
175
|
Order order = orderMapper.selectById(id);
|
|
269
176
|
Assert.notNull(order, () -> new LeException("订单不存在"));
|
|
270
177
|
|
|
271
178
|
// 校验数据归属
|
|
272
|
-
Long currentUserId = TokenManager.getSubjectId()
|
|
273
|
-
() -> new LeException("用户未登录")
|
|
274
|
-
);
|
|
179
|
+
Long currentUserId = TokenManager.getSubjectId()
|
|
180
|
+
.orElseThrow(() -> new LeException("用户未登录"));
|
|
275
181
|
if (!order.getUserId().equals(currentUserId)) {
|
|
276
182
|
throw new LeException("无权操作该订单");
|
|
277
183
|
}
|
|
278
184
|
|
|
279
|
-
// 执行删除
|
|
280
185
|
orderMapper.deleteById(id);
|
|
281
186
|
}
|
|
282
187
|
```
|
|
283
188
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
### 日志脱敏
|
|
189
|
+
### 最小权限原则
|
|
287
190
|
|
|
288
191
|
```java
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
192
|
+
@RequiresAuthentication
|
|
193
|
+
@RequiresPermissions("order:view")
|
|
194
|
+
public PageVO<OrderVO> pageList(OrderPageParam param) {
|
|
195
|
+
Long userId = TokenManager.getSubjectId()
|
|
196
|
+
.orElseThrow(() -> new LeException("用户未登录"));
|
|
197
|
+
param.setUserId(userId); // 限制只查自己的数据
|
|
198
|
+
return orderService.pageList(param);
|
|
199
|
+
}
|
|
294
200
|
```
|
|
295
201
|
|
|
296
|
-
|
|
202
|
+
---
|
|
297
203
|
|
|
298
|
-
|
|
299
|
-
// 手机号脱敏
|
|
300
|
-
public static String maskMobile(String mobile) {
|
|
301
|
-
if (StrUtil.isBlank(mobile) || mobile.length() != 11) {
|
|
302
|
-
return mobile;
|
|
303
|
-
}
|
|
304
|
-
return mobile.substring(0, 3) + "****" + mobile.substring(7);
|
|
305
|
-
}
|
|
204
|
+
## SQL 注入防护
|
|
306
205
|
|
|
307
|
-
|
|
308
|
-
public static String maskIdCard(String idCard) {
|
|
309
|
-
if (StrUtil.isBlank(idCard) || idCard.length() < 8) {
|
|
310
|
-
return idCard;
|
|
311
|
-
}
|
|
312
|
-
return idCard.substring(0, 4) + "**********" + idCard.substring(idCard.length() - 4);
|
|
313
|
-
}
|
|
206
|
+
### MyBatis XML 必须用 `#{}`
|
|
314
207
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
208
|
+
```xml
|
|
209
|
+
<!-- 正确:参数化查询 -->
|
|
210
|
+
<select id="selectByName" resultType="User">
|
|
211
|
+
SELECT id, name, mobile FROM user WHERE name = #{name}
|
|
212
|
+
</select>
|
|
213
|
+
|
|
214
|
+
<!-- 错误:${} 有注入风险 -->
|
|
215
|
+
<select id="selectByName" resultType="User">
|
|
216
|
+
SELECT * FROM user WHERE name = '${name}'
|
|
217
|
+
</select>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 禁止 SELECT *
|
|
221
|
+
|
|
222
|
+
```xml
|
|
223
|
+
<!-- 错误 -->
|
|
224
|
+
SELECT * FROM user WHERE status = #{status}
|
|
225
|
+
|
|
226
|
+
<!-- 正确:明确指定字段 -->
|
|
227
|
+
SELECT id, name, mobile, status FROM user WHERE status = #{status}
|
|
322
228
|
```
|
|
323
229
|
|
|
324
|
-
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## VO 敏感字段处理
|
|
325
233
|
|
|
326
234
|
```java
|
|
327
235
|
@Data
|
|
328
236
|
public class UserVO {
|
|
329
|
-
// 密码绝不返回
|
|
330
237
|
@JsonIgnore
|
|
331
|
-
private String password;
|
|
238
|
+
private String password; // 密码绝不返回
|
|
332
239
|
|
|
333
|
-
// 手机号脱敏
|
|
334
240
|
@JsonSerialize(using = MobileSerializer.class)
|
|
335
|
-
private String mobile;
|
|
241
|
+
private String mobile; // 手机号脱敏
|
|
336
242
|
|
|
337
|
-
// 身份证脱敏
|
|
338
243
|
@JsonSerialize(using = IdCardSerializer.class)
|
|
339
|
-
private String idCard;
|
|
340
|
-
}
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## BCrypt 密码安全
|
|
344
|
-
|
|
345
|
-
```java
|
|
346
|
-
@Autowired
|
|
347
|
-
private PasswordEncoder passwordEncoder;
|
|
348
|
-
|
|
349
|
-
// 注册时加密密码(BCrypt)
|
|
350
|
-
public void register(UserParam param) {
|
|
351
|
-
User user = new User();
|
|
352
|
-
user.setUsername(param.getUsername());
|
|
353
|
-
user.setPassword(passwordEncoder.encode(param.getPassword()));
|
|
354
|
-
userMapper.insert(user);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// 登录时验证密码
|
|
358
|
-
public void login(String username, String password) {
|
|
359
|
-
User user = userMapper.selectByUsername(username);
|
|
360
|
-
if (user == null) {
|
|
361
|
-
throw new LeException("用户不存在");
|
|
362
|
-
}
|
|
363
|
-
if (!passwordEncoder.matches(password, user.getPassword())) {
|
|
364
|
-
throw new LeException("密码错误");
|
|
365
|
-
}
|
|
244
|
+
private String idCard; // 身份证脱敏
|
|
366
245
|
}
|
|
367
246
|
```
|
|
368
247
|
|
|
369
|
-
|
|
248
|
+
### 日志脱敏
|
|
370
249
|
|
|
371
250
|
```java
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
long timestamp = request.getTimestamp();
|
|
375
|
-
long now = System.currentTimeMillis();
|
|
376
|
-
if (Math.abs(now - timestamp) > 5 * 60 * 1000) {
|
|
377
|
-
throw new LeException("请求已过期");
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// 2. 校验签名
|
|
381
|
-
String sign = request.getSign();
|
|
382
|
-
String expectedSign = generateSign(request);
|
|
383
|
-
if (!sign.equals(expectedSign)) {
|
|
384
|
-
throw new LeException("签名验证失败");
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// 3. 校验 nonce(防止重放)
|
|
388
|
-
String nonce = request.getNonce();
|
|
389
|
-
if (redisTemplate.hasKey("nonce:" + nonce)) {
|
|
390
|
-
throw new LeException("请求已处理");
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// 4. 记录 nonce(5 分钟有效)
|
|
394
|
-
redisTemplate.opsForValue().set("nonce:" + nonce, "1", 5, TimeUnit.MINUTES);
|
|
251
|
+
// 错误:记录敏感信息
|
|
252
|
+
log.info("用户登录, username:{}, password:{}", username, password);
|
|
395
253
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
254
|
+
// 正确:不记录密码
|
|
255
|
+
log.info("用户登录, username:{}", username);
|
|
399
256
|
```
|
|
400
257
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
```java
|
|
404
|
-
public void checkRateLimit(Long userId, String api, int limit, int seconds) {
|
|
405
|
-
String key = String.format("rate:%d:%s", userId, api);
|
|
406
|
-
Integer count = RedisUtil.incr(key, (long) seconds);
|
|
407
|
-
|
|
408
|
-
// 首次请求已由 incr 设置过期时间
|
|
409
|
-
if (count > limit) {
|
|
410
|
-
throw new LeException("请求过于频繁,请稍后重试");
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
```
|
|
258
|
+
---
|
|
414
259
|
|
|
415
260
|
## 输入验证
|
|
416
261
|
|
|
@@ -434,87 +279,28 @@ public class UserParam {
|
|
|
434
279
|
}
|
|
435
280
|
```
|
|
436
281
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
```java
|
|
440
|
-
public String uploadFile(MultipartFile file) {
|
|
441
|
-
// 1. 校验文件是否为空
|
|
442
|
-
if (file == null || file.isEmpty()) {
|
|
443
|
-
throw new LeException("文件不能为空");
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// 2. 校验文件大小(10MB)
|
|
447
|
-
if (file.getSize() > 10 * 1024 * 1024) {
|
|
448
|
-
throw new LeException("文件大小不能超过10MB");
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// 3. 白名单校验文件类型(白名单优于黑名单)
|
|
452
|
-
String contentType = file.getContentType();
|
|
453
|
-
List<String> allowedTypes = Arrays.asList("image/jpeg", "image/png", "image/gif");
|
|
454
|
-
if (!allowedTypes.contains(contentType)) {
|
|
455
|
-
throw new LeException("不支持的文件类型");
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// 4. 校验文件扩展名
|
|
459
|
-
String filename = file.getOriginalFilename();
|
|
460
|
-
String extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
|
|
461
|
-
List<String> allowedExtensions = Arrays.asList("jpg", "jpeg", "png", "gif");
|
|
462
|
-
if (!allowedExtensions.contains(extension)) {
|
|
463
|
-
throw new LeException("不支持的文件扩展名");
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// 5. 生成安全的文件名(避免路径穿越)
|
|
467
|
-
String safeFilename = UUID.randomUUID().toString() + "." + extension;
|
|
468
|
-
|
|
469
|
-
return fileService.upload(file, safeFilename);
|
|
470
|
-
}
|
|
471
|
-
```
|
|
282
|
+
---
|
|
472
283
|
|
|
473
|
-
##
|
|
284
|
+
## 限流防护
|
|
474
285
|
|
|
475
286
|
```java
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
param.setUserId(userId);
|
|
484
|
-
return orderService.pageList(param);
|
|
287
|
+
// Redis 计数器限流
|
|
288
|
+
public void checkRateLimit(Long userId, String api, int limit, int seconds) {
|
|
289
|
+
String key = String.format("rate:%d:%s", userId, api);
|
|
290
|
+
Integer count = RedisUtil.incr(key, (long) seconds);
|
|
291
|
+
if (count > limit) {
|
|
292
|
+
throw new LeException("请求过于频繁,请稍后重试");
|
|
293
|
+
}
|
|
485
294
|
}
|
|
486
295
|
```
|
|
487
296
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
```java
|
|
491
|
-
// ✅ 使用 SecureRandom(密码学安全)
|
|
492
|
-
SecureRandom random = new SecureRandom();
|
|
493
|
-
byte[] bytes = new byte[32];
|
|
494
|
-
random.nextBytes(bytes);
|
|
495
|
-
|
|
496
|
-
// ❌ 不安全:使用 Random(可预测)
|
|
497
|
-
Random random = new Random();
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
## 与 RuoYi-Plus 的区别
|
|
501
|
-
|
|
502
|
-
| 特性 | RuoYi-Plus | leniu-tengyun-core |
|
|
503
|
-
|------|-----------|-------------------|
|
|
504
|
-
| 权限注解 | `@SaCheckPermission("system:user:list")` | `@RequiresPermissions("system:user:list")` |
|
|
505
|
-
| 角色注解 | `@SaCheckRole("admin")` | `@RequiresRoles("admin")` |
|
|
506
|
-
| 登录检查 | `StpUtil.isLogin()` | `TokenManager.isLogin()` |
|
|
507
|
-
| 获取用户 ID | `StpUtil.getLoginIdAsLong()` | `TokenManager.getSubjectId()` |
|
|
508
|
-
| 权限检查 | `StpUtil.hasPermission("key")` | `TokenManager.hasPermission("key")` |
|
|
509
|
-
| 角色检查 | `StpUtil.hasRole("admin")` | `TokenManager.hasRole("admin")` |
|
|
510
|
-
| 登出 | `StpUtil.logout()` | `TokenManager.logout()` |
|
|
297
|
+
---
|
|
511
298
|
|
|
512
299
|
## 注意事项
|
|
513
300
|
|
|
514
|
-
1.
|
|
515
|
-
2.
|
|
516
|
-
3. `
|
|
517
|
-
4.
|
|
518
|
-
5.
|
|
519
|
-
6.
|
|
520
|
-
7. 限流 key 格式:`rate:{userId}:{api}`,防重放 key 格式:`nonce:{nonce}`
|
|
301
|
+
1. 权限和角色数据自动缓存到 Redis,通过 `clearPermission/clearRole` 手动清除
|
|
302
|
+
2. `TokenManager` 方法需在已登录上下文中使用
|
|
303
|
+
3. `@RequiresGuest` 用于公开接口,不需要登录
|
|
304
|
+
4. MyBatis XML 中必须使用 `#{}` 而非 `${}`
|
|
305
|
+
5. VO 中密码字段必须 `@JsonIgnore`,手机号等用序列化脱敏
|
|
306
|
+
6. 限流 key 格式:`rate:{userId}:{api}`
|