Java分页安全流程规范

wen java案例 29

本文目录导读:

Java分页安全流程规范

  1. 分页安全基础规范
  2. 安全防护措施
  3. 防爬虫与限流
  4. 性能与安全优化
  5. 完整使用示例
  6. 安全规范检查清单

我来为您详细介绍Java分页安全流程规范,这是一个重要的企业级开发规范。

分页安全基础规范

1 统一分页参数规范

public class PageRequest {
    // 最小页码为1
    private static final int MIN_PAGE = 1;
    // 最大每页条数限制
    private static final int MAX_PAGE_SIZE = 100;
    // 默认每页条数
    private static final int DEFAULT_PAGE_SIZE = 20;
    @Min(value = 1, message = "页码必须大于0")
    private Integer pageNum;
    @Min(value = 1, message = "每页条数必须大于0")
    @Max(value = 100, message = "每页条数不能超过100")
    private Integer pageSize;
    // 排序字段白名单
    private String sortField;
    private String sortOrder; // ASC/DESC
    // 构造函数含参数校验
    public PageRequest(Integer pageNum, Integer pageSize) {
        this.pageNum = pageNum != null ? Math.max(MIN_PAGE, pageNum) : MIN_PAGE;
        this.pageSize = pageSize != null ? Math.min(MAX_PAGE_SIZE, Math.max(1, pageSize)) : DEFAULT_PAGE_SIZE;
    }
}

2 统一响应格式

public class PageResult<T> {
    private List<T> records;
    private long total;
    private int currentPage;
    private int pageSize;
    private int totalPages;
    private boolean hasMore;
    // 是否脱敏
    private boolean desensitized;
}

安全防护措施

1 SQL注入防护

@Component
public class SafePageHelper {
    // 排序字段白名单
    private static final Set<String> ALLOWED_SORT_FIELDS = Set.of(
        "id", "createTime", "updateTime", "name", "status"
    );
    // 排序方向白名单
    private static final Set<String> ALLOWED_ORDER = Set.of("ASC", "DESC", "asc", "desc");
    public String validateSortField(String sortField) {
        if (sortField == null || sortField.trim().isEmpty()) {
            return "id";
        }
        // 1. 移除可能存在SQL注入的字符
        sortField = sortField.replaceAll("[^a-zA-Z0-9_]", "");
        // 2. 白名单校验
        if (!ALLOWED_SORT_FIELDS.contains(sortField)) {
            return "id"; // 默认排序字段
        }
        return sortField;
    }
    public String validateSortOrder(String sortOrder) {
        if (sortOrder == null || !ALLOWED_ORDER.contains(sortOrder.toUpperCase())) {
            return "DESC";
        }
        return sortOrder.toUpperCase();
    }
}

2 数据权限控制

@Service
public class SecurePageService {
    public PageResult<User> queryUsers(PageRequest pageRequest, UserContext user) {
        // 1. 参数校验与清洗
        validatePageRequest(pageRequest);
        // 2. 转换安全参数
        SafePageParams safeParams = buildSafeParams(pageRequest);
        // 3. 数据权限过滤
        // 普通用户只能看到自己的数据
        // 管理员可以看到部门数据
        // 超级管理员可以看到所有数据
        DataPermission permission = buildDataPermission(user);
        // 4. 执行查询(使用MyBatis-Plus的分页)
        Page<User> page = new Page<>(safeParams.getPageNum(), safeParams.getPageSize());
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
            .eq(permission.isSelfOnly(), User::getUserId, user.getUserId())
            .in(permission.hasDeptLimit(), User::getDeptId, user.getDeptIds())
            .orderBy(permission.isAdmin(), User::getCreateTime, false);
        // 5. 结果脱敏
        Page<User> result = userMapper.selectPage(page, wrapper);
        return buildSecurePageResult(result, user);
    }
}

防爬虫与限流

1 频率控制

@Component
public class PageAccessController {
    @Autowired
    private RedisTemplate redisTemplate;
    // 使用Redis实现滑动窗口限流
    public boolean checkPageAccess(String userId, String apiPath) {
        String key = "page:access:" + userId + ":" + apiPath;
        // 滑动窗口大小:60秒
        long windowSize = 60L;
        // 最大请求次数
        long maxRequests = 30L;
        long currentTime = System.currentTimeMillis() / 1000;
        long windowStart = currentTime - windowSize;
        // 移除窗口外的记录
        redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
        // 获取当前窗口请求数
        Long count = redisTemplate.opsForZSet().zCard(key);
        if (count != null && count >= maxRequests) {
            return false; // 超出限制
        }
        // 添加当前请求
        redisTemplate.opsForZSet().add(key, String.valueOf(currentTime), currentTime);
        redisTemplate.expire(key, windowSize + 10, TimeUnit.SECONDS);
        return true;
    }
}

2 敏感数据脱敏

public class PageDataDesensitizer {
    // 手机号脱敏
    public String maskPhone(String phone) {
        if (phone == null || phone.length() < 7) {
            return phone;
        }
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
    // 邮箱脱敏
    public String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        String[] parts = email.split("@");
        String name = parts[0];
        if (name.length() <= 3) {
            return name.charAt(0) + "***@" + parts[1];
        }
        return name.substring(0, 3) + "***@" + parts[1];
    }
    // 分页结果脱敏
    public <T> PageResult<T> desensitizePage(PageResult<T> pageResult, 
                                              Class<T> clazz, 
                                              UserContext user) {
        if (user.isAdmin()) {
            return pageResult; // 管理员不脱敏
        }
        List<T> records = pageResult.getRecords();
        // 根据用户角色和数据类型进行不同级别的脱敏
        records = records.stream()
            .map(record -> desensitizeRecord(record, clazz, user))
            .collect(Collectors.toList());
        pageResult.setRecords(records);
        pageResult.setDesensitized(true);
        return pageResult;
    }
}

性能与安全优化

1 缓存策略

@Service
public class CachedPageService {
    // 分页查询缓存Key生成
    public String buildPageCacheKey(String queryType, PageRequest pageRequest, 
                                      Map<String, Object> filters) {
        StringBuilder sb = new StringBuilder();
        sb.append("page:").append(queryType)
          .append(":p").append(pageRequest.getPageNum())
          .append("-s").append(pageRequest.getPageSize());
        if (filters != null) {
            filters.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .forEach(entry -> sb.append(":")
                  .append(entry.getKey())
                  .append("=")
                  .append(entry.getValue()));
        }
        return sb.toString();
    }
    // 带缓存的查询
    public PageResult<Data> queryWithCache(String type, PageRequest pageRequest, 
                                            UserContext user) {
        String cacheKey = buildPageCacheKey(type, pageRequest, user.getFilters());
        // 1. 尝试从缓存获取
        PageResult<Data> cachedResult = redisTemplate.opsForValue().get(cacheKey);
        if (cachedResult != null) {
            return cachedResult;
        }
        // 2. 数据库查询
        PageResult<Data> result = queryFromDatabase(type, pageRequest, user);
        // 3. 设置缓存(考虑过期时间)
        long ttl = calculateCacheTTL(type, pageRequest);
        redisTemplate.opsForValue().set(cacheKey, result, ttl, TimeUnit.MINUTES);
        return result;
    }
}

2 审计日志

@Component
public class PageAuditLogger {
    @Autowired
    private AuditLogRepository auditLogRepository;
    public void logPageAccess(String userId, String apiPath, 
                                PageRequest pageRequest, 
                                boolean success) {
        PageAuditLog log = new PageAuditLog();
        log.setUserId(userId);
        log.setApiPath(apiPath);
        log.setPageNum(pageRequest.getPageNum());
        log.setPageSize(pageRequest.getPageSize());
        log.setRequestTime(new Date());
        log.setSuccess(success);
        log.setIpAddress(getClientIp());
        log.setUserAgent(getUserAgent());
        // 异步记录审计日志
        CompletableFuture.runAsync(() -> {
            auditLogRepository.save(log);
        });
    }
    // 检测异常行为
    public boolean detectAnomaly(String userId, String apiPath) {
        // 检查是否存在异常的大量数据导出
        // 检查是否存在频繁的深度分页
        // 检查是否存在疑似爬虫行为
        return checkExcessiveDataExport(userId) 
            || checkDeepPaginationAttack(userId)
            || checkCrawlBehavior(userId);
    }
}

完整使用示例

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private SecurePageService pageService;
    @Autowired
    private PageAccessController accessController;
    @Autowired
    private PageAuditLogger auditLogger;
    @GetMapping("/page")
    public Result<PageResult<UserDTO>> queryUsers(
            @Valid PageRequest pageRequest,
            @RequestParam(required = false) String keyword,
            @AuthenticationPrincipal UserContext user) {
        // 1. 访问频率检查
        if (!accessController.checkPageAccess(user.getUserId(), "/api/users/page")) {
            auditLogger.logPageAccess(user.getUserId(), "/api/users/page", 
                                      pageRequest, false);
            return Result.error("请求过于频繁");
        }
        // 2. 检测异常行为
        if (auditLogger.detectAnomaly(user.getUserId(), "/api/users/page")) {
            auditLogger.logPageAccess(user.getUserId(), "/api/users/page", 
                                      pageRequest, false);
            return Result.error("检测到异常访问行为");
        }
        // 3. 执行安全分页查询
        PageResult<User> queryResult = pageService.queryUsers(pageRequest, user);
        // 4. 结果脱敏
        PageResult<UserDTO> safeResult = dataDesensitizer
            .desensitizePage(queryResult, UserDTO.class, user);
        // 5. 审计日志
        auditLogger.logPageAccess(user.getUserId(), "/api/users/page", 
                                  pageRequest, true);
        return Result.success(safeResult);
    }
}

安全规范检查清单

检查项 要求 优先级
参数校验 页码≥1,每页条数1-100 强制
SQL注入防护 排序字段白名单验证 强制
数据权限 按角色过滤数据 强制
访问频率控制 单用户每分钟≤30次 推荐
敏感数据脱敏 按用户角色脱敏 强制
深度分页限制 最大偏移量≤10000 推荐
缓存策略 合理设置缓存TTL 推荐
审计日志 记录所有分页请求 强制
异常检测 检测爬虫等异常行为 推荐

Java分页安全流程规范不仅要关注功能实现,更要注重:

  1. 输入验证:严格的参数校验和格式化
  2. 权限控制:基于角色的数据访问限制
  3. 安全防护:SQL注入、XSS等攻击防护
  4. 性能优化:合理的缓存和索引策略
  5. 监控审计:全面的日志记录和异常检测

这些规范能有效防止数据泄露、接口滥用等安全风险。

抱歉,评论功能暂时关闭!