docs: 创建安全设计文档
This commit is contained in:
@@ -0,0 +1,626 @@
|
||||
# 健身房管理系统安全设计文档
|
||||
|
||||
> 文档编号:GYM-SEC-DESIGN-001
|
||||
> 版本:v1.0
|
||||
> 创建日期:2026-03-08
|
||||
> 最后更新日期:2026-03-08
|
||||
> 作者:张翔
|
||||
> 状态:正式发布
|
||||
|
||||
## 文档修订历史
|
||||
|
||||
| 版本 | 日期 | 作者 | 修订内容 |
|
||||
| ---- | ---------- | ---- | -------- |
|
||||
| v1.0 | 2026-03-08 | 张翔 | 创建安全设计文档 |
|
||||
|
||||
## 参考文档
|
||||
|
||||
- OWASP Top 10 安全规范
|
||||
- Spring Security 官方文档
|
||||
- GDPR 数据保护条例
|
||||
- 网络安全等级保护 2.0
|
||||
|
||||
---
|
||||
|
||||
## 一、安全架构设计
|
||||
|
||||
### 1.1 安全分层
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 应用层安全 │
|
||||
│ (认证、授权、输入验证、输出编码) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 数据层安全 │
|
||||
│ (加密、脱敏、审计、备份) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 基础设施安全 │
|
||||
│ (网络安全、主机安全、容器安全) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 安全原则
|
||||
|
||||
1. **纵深防御**:多层安全防护
|
||||
2. **最小权限**:只授予必要权限
|
||||
3. **默认安全**:默认配置即安全
|
||||
4. **零信任**:始终验证,永不信任
|
||||
5. **安全审计**:所有操作可追溯
|
||||
|
||||
---
|
||||
|
||||
## 二、认证与授权
|
||||
|
||||
### 2.1 认证机制
|
||||
|
||||
#### 2.1.1 JWT Token 认证
|
||||
|
||||
**Token 生成**:
|
||||
```java
|
||||
@Component
|
||||
public class JwtTokenProvider {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private long expiration;
|
||||
|
||||
public String generateToken(Authentication auth) {
|
||||
UserPrincipal principal = (UserPrincipal) auth.getPrincipal();
|
||||
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(principal.getId().toString())
|
||||
.claim("tenantId", principal.getTenantId())
|
||||
.claim("storeId", principal.getStoreId())
|
||||
.claim("roles", principal.getRoles())
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.signWith(SignatureAlgorithm.HS512, secretKey)
|
||||
.compact();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Token 验证**:
|
||||
```java
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private JwtTokenProvider tokenProvider;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
String jwt = getJwtFromRequest(request);
|
||||
|
||||
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
|
||||
Authentication auth = tokenProvider.getAuthentication(jwt);
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Could not set user authentication", ex);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.1.2 Token 刷新机制
|
||||
|
||||
**双 Token 机制**:
|
||||
- Access Token:有效期 2 小时
|
||||
- Refresh Token:有效期 7 天
|
||||
|
||||
**刷新流程**:
|
||||
```java
|
||||
@PostMapping("/refresh")
|
||||
public Mono<ApiResponse<TokenResponse>> refreshToken(
|
||||
@RequestBody RefreshTokenRequest request) {
|
||||
return authService.refreshToken(request.getRefreshToken())
|
||||
.map(tokens -> ApiResponse.success(tokens));
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 授权机制
|
||||
|
||||
#### 2.2.1 基于角色的访问控制 (RBAC)
|
||||
|
||||
**角色定义**:
|
||||
```java
|
||||
public enum Role {
|
||||
SUPER_ADMIN, // 超级管理员
|
||||
TENANT_ADMIN, // 租户管理员
|
||||
STORE_MANAGER, // 店长
|
||||
COACH, // 教练
|
||||
MEMBER // 会员
|
||||
}
|
||||
```
|
||||
|
||||
**权限配置**:
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterFilterChain securityFilterChain(
|
||||
ServerHttpSecurity http) {
|
||||
http
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.pathMatchers("/api/v1/auth/**").permitAll()
|
||||
.pathMatchers("/api/v1/admin/**").hasRole("ADMIN")
|
||||
.pathMatchers("/api/v1/members/**").hasAnyRole("ADMIN", "COACH")
|
||||
.pathMatchers("/api/v1/my/**").authenticated()
|
||||
.anyExchange().permitAll()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.2 数据权限隔离
|
||||
|
||||
**租户隔离**:
|
||||
```java
|
||||
@Component
|
||||
public class TenantInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler) {
|
||||
String tenantId = request.getHeader("X-Tenant-ID");
|
||||
if (StringUtils.isEmpty(tenantId)) {
|
||||
throw new UnauthorizedException("缺少租户标识");
|
||||
}
|
||||
TenantContext.setTenantId(tenantId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler,
|
||||
Exception ex) {
|
||||
TenantContext.clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、数据安全
|
||||
|
||||
### 3.1 数据加密
|
||||
|
||||
#### 3.1.1 敏感数据加密存储
|
||||
|
||||
**加密算法**:AES-256-GCM
|
||||
|
||||
**加密工具类**:
|
||||
```java
|
||||
@Component
|
||||
public class EncryptionUtil {
|
||||
|
||||
@Value("${encryption.key}")
|
||||
private String encryptionKey;
|
||||
|
||||
public String encrypt(String plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(
|
||||
encryptionKey.getBytes(), "AES");
|
||||
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(
|
||||
128, generateIV());
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
|
||||
byte[] cipherText = cipher.doFinal(plaintext.getBytes());
|
||||
|
||||
return Base64.getEncoder().encodeToString(cipherText);
|
||||
}
|
||||
|
||||
public String decrypt(String cipherText) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(
|
||||
encryptionKey.getBytes(), "AES");
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec,
|
||||
new GCMParameterSpec(128, generateIV()));
|
||||
byte[] plainText = cipher.doFinal(
|
||||
Base64.getDecoder().decode(cipherText));
|
||||
|
||||
return new String(plainText);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**加密字段**:
|
||||
- 手机号
|
||||
- 身份证号
|
||||
- 银行卡号
|
||||
- 地址
|
||||
|
||||
#### 3.1.2 密码加密
|
||||
|
||||
**BCrypt 加密**:
|
||||
```java
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(12);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 数据脱敏
|
||||
|
||||
#### 3.2.1 脱敏规则
|
||||
|
||||
**手机号脱敏**:
|
||||
```java
|
||||
public class DesensitizationUtil {
|
||||
|
||||
public static String maskPhone(String phone) {
|
||||
if (StringUtils.isEmpty(phone)) {
|
||||
return "";
|
||||
}
|
||||
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
|
||||
}
|
||||
|
||||
public static String maskIdCard(String idCard) {
|
||||
if (StringUtils.isEmpty(idCard)) {
|
||||
return "";
|
||||
}
|
||||
return idCard.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.2 JSON 序列化脱敏
|
||||
|
||||
**自定义注解**:
|
||||
```java
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@JsonSerialize(using = DesensitizationSerializer.class)
|
||||
public @interface Desensitization {
|
||||
DesensitizationType type();
|
||||
}
|
||||
|
||||
public enum DesensitizationType {
|
||||
PHONE, // 手机号
|
||||
ID_CARD, // 身份证
|
||||
BANK_CARD, // 银行卡
|
||||
ADDRESS // 地址
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
```java
|
||||
public class MemberDTO {
|
||||
|
||||
@Desensitization(type = DesensitizationType.PHONE)
|
||||
private String phone;
|
||||
|
||||
@Desensitization(type = DesensitizationType.ID_CARD)
|
||||
private String idCard;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 数据备份
|
||||
|
||||
#### 3.3.1 备份策略
|
||||
|
||||
**全量备份**:
|
||||
- 频率:每天凌晨 2 点
|
||||
- 保留:最近 30 天
|
||||
- 存储:异地灾备中心
|
||||
|
||||
**增量备份**:
|
||||
- 频率:每小时
|
||||
- 保留:最近 7 天
|
||||
- 存储:本地高速存储
|
||||
|
||||
#### 3.3.2 恢复演练
|
||||
|
||||
- 频率:每季度一次
|
||||
- 范围:随机抽取 10% 数据
|
||||
- 验证:数据完整性校验
|
||||
|
||||
---
|
||||
|
||||
## 四、网络安全
|
||||
|
||||
### 4.1 HTTPS 强制
|
||||
|
||||
**配置**:
|
||||
```yaml
|
||||
server:
|
||||
ssl:
|
||||
enabled: true
|
||||
key-store: classpath:keystore.p12
|
||||
key-store-password: ${SSL_KEY_PASSWORD}
|
||||
key-store-type: PKCS12
|
||||
```
|
||||
|
||||
**HTTP 重定向**:
|
||||
```java
|
||||
@Bean
|
||||
public SecurityWebFilterFilterChain securityFilterChain(
|
||||
ServerHttpSecurity http) {
|
||||
http
|
||||
.redirectHttpsRedirect(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 CORS 配置
|
||||
|
||||
**跨域配置**:
|
||||
```java
|
||||
@Bean
|
||||
public WebMvcConfigurer corsConfigurer() {
|
||||
return new WebMvcConfigurer() {
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("https://yourdomain.com")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 限流与防 DDOS
|
||||
|
||||
**限流配置**:
|
||||
```yaml
|
||||
resilience4j:
|
||||
ratelimiter:
|
||||
instances:
|
||||
apiRateLimiter:
|
||||
limit-for-period: 100
|
||||
limit-refresh-period: 1s
|
||||
timeout-duration: 0
|
||||
|
||||
loginRateLimiter:
|
||||
limit-for-period: 5
|
||||
limit-refresh-period: 1m
|
||||
timeout-duration: 0
|
||||
```
|
||||
|
||||
**IP 黑名单**:
|
||||
```java
|
||||
@Component
|
||||
public class IpBlacklistFilter implements Filter {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request,
|
||||
ServletResponse response,
|
||||
FilterChain chain) {
|
||||
String ip = getClientIp(request);
|
||||
|
||||
if (isBlacklisted(ip)) {
|
||||
((HttpServletResponse) response).sendError(403);
|
||||
return;
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、输入验证与输出编码
|
||||
|
||||
### 5.1 输入验证
|
||||
|
||||
**请求体验证**:
|
||||
```java
|
||||
public class CreateMemberRequest {
|
||||
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
@NotBlank(message = "姓名不能为空")
|
||||
@Size(min = 1, max = 50, message = "姓名长度不能超过 50 个字符")
|
||||
private String name;
|
||||
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
@Min(value = 0, message = "年龄不能小于 0")
|
||||
@Max(value = 150, message = "年龄不能大于 150")
|
||||
private Integer age;
|
||||
}
|
||||
```
|
||||
|
||||
**SQL 注入防护**:
|
||||
```java
|
||||
// ❌ 错误示例
|
||||
@Query("SELECT m FROM Member m WHERE m.phone = :phone")
|
||||
Member findByPhone(@Param("phone") String phone);
|
||||
|
||||
// ✅ 正确示例(使用参数化查询)
|
||||
@Query("SELECT m FROM Member m WHERE m.phone = :phone")
|
||||
Member findByPhone(@Param("phone") String phone);
|
||||
```
|
||||
|
||||
**XSS 防护**:
|
||||
```java
|
||||
@Component
|
||||
public class XssFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request,
|
||||
ServletResponse response,
|
||||
FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
XssHttpServletRequestWrapper xssRequest =
|
||||
new XssHttpServletRequestWrapper(
|
||||
(HttpServletRequest) request);
|
||||
chain.doFilter(xssRequest, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 输出编码
|
||||
|
||||
**HTML 编码**:
|
||||
```java
|
||||
public class HtmlUtil {
|
||||
|
||||
public static String escapeHtml(String html) {
|
||||
return StringEscapeUtils.escapeHtml4(html);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、安全审计
|
||||
|
||||
### 6.1 审计日志
|
||||
|
||||
**审计内容**:
|
||||
- 登录/登出
|
||||
- 创建/更新/删除操作
|
||||
- 数据导出
|
||||
- 权限变更
|
||||
|
||||
**日志格式**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-03-08T10:30:00Z",
|
||||
"userId": 1,
|
||||
"action": "CREATE_MEMBER",
|
||||
"resource": "member",
|
||||
"resourceId": 123,
|
||||
"ip": "192.168.1.100",
|
||||
"userAgent": "Mozilla/5.0...",
|
||||
"result": "SUCCESS",
|
||||
"details": {...}
|
||||
}
|
||||
```
|
||||
|
||||
**审计注解**:
|
||||
```java
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AuditLog {
|
||||
String action();
|
||||
String resource();
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
```java
|
||||
@AuditLog(action = "CREATE", resource = "member")
|
||||
public Mono<Member> createMember(CreateMemberRequest request) {
|
||||
return memberRepository.save(request.toEntity());
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 日志存储
|
||||
|
||||
**存储策略**:
|
||||
- 热存储:最近 30 天,Elasticsearch
|
||||
- 冷存储:30-180 天,对象存储
|
||||
- 归档:180 天以上,磁带库
|
||||
|
||||
**日志保护**:
|
||||
- 完整性:数字签名
|
||||
- 机密性:加密存储
|
||||
- 可用性:多副本备份
|
||||
|
||||
---
|
||||
|
||||
## 七、安全监控
|
||||
|
||||
### 7.1 监控指标
|
||||
|
||||
**认证监控**:
|
||||
- 登录成功率
|
||||
- 登录失败次数
|
||||
- Token 刷新率
|
||||
- 异常登录行为
|
||||
|
||||
**授权监控**:
|
||||
- 权限拒绝次数
|
||||
- 越权访问尝试
|
||||
- 敏感操作频率
|
||||
|
||||
**数据监控**:
|
||||
- 敏感数据访问
|
||||
- 大批量数据导出
|
||||
- 异常数据修改
|
||||
|
||||
### 7.2 告警规则
|
||||
|
||||
**告警级别**:
|
||||
- P0(紧急):系统被入侵、数据泄露
|
||||
- P1(严重):大规模认证失败、DDOS 攻击
|
||||
- P2(警告):异常登录行为、权限异常
|
||||
- P3(提示):配置变更、版本升级
|
||||
|
||||
**告警渠道**:
|
||||
- 短信:P0、P1
|
||||
- 邮件:P1、P2
|
||||
- 钉钉/企业微信:P2、P3
|
||||
|
||||
---
|
||||
|
||||
## 八、合规性
|
||||
|
||||
### 8.1 GDPR 合规
|
||||
|
||||
**数据主体权利**:
|
||||
- 知情权:明确告知数据收集目的
|
||||
- 访问权:用户可查询个人数据
|
||||
- 更正权:用户可修改个人数据
|
||||
- 删除权:用户可申请删除数据
|
||||
- 可携带权:支持数据导出
|
||||
|
||||
**数据保护措施**:
|
||||
- 数据最小化:只收集必要数据
|
||||
- 目的限制:仅用于声明的目的
|
||||
- 存储限制:到期自动删除
|
||||
- 安全保障:加密、访问控制
|
||||
|
||||
### 8.2 等保 2.0 合规
|
||||
|
||||
**技术要求**:
|
||||
- 身份鉴别:多因素认证
|
||||
- 访问控制:最小权限原则
|
||||
- 安全审计:操作可追溯
|
||||
- 入侵防范:实时监测告警
|
||||
- 数据完整性:校验和验证
|
||||
- 数据保密性:加密传输存储
|
||||
|
||||
**管理要求**:
|
||||
- 安全管理制度
|
||||
- 安全管理机构
|
||||
- 人员安全管理
|
||||
- 系统建设管理
|
||||
- 系统运维管理
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
Reference in New Issue
Block a user