本文目录导读:

我来为您详细介绍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分页安全流程规范不仅要关注功能实现,更要注重:
- 输入验证:严格的参数校验和格式化
- 权限控制:基于角色的数据访问限制
- 安全防护:SQL注入、XSS等攻击防护
- 性能优化:合理的缓存和索引策略
- 监控审计:全面的日志记录和异常检测
这些规范能有效防止数据泄露、接口滥用等安全风险。