# 健身房管理系统安全设计文档 > 文档编号: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> 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 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 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 合规 **技术要求**: - 身份鉴别:多因素认证 - 访问控制:最小权限原则 - 安全审计:操作可追溯 - 入侵防范:实时监测告警 - 数据完整性:校验和验证 - 数据保密性:加密传输存储 **管理要求**: - 安全管理制度 - 安全管理机构 - 人员安全管理 - 系统建设管理 - 系统运维管理 --- **文档结束**