饿了么CPS系统中Java后端防止SQL注入与XSS攻击的实战技巧
·
饿了么CPS系统中Java后端防止SQL注入与XSS攻击的实战技巧
在饿了么CPS系统中,订单查询、用户信息管理、佣金报表导出等接口若未做安全防护,极易遭受SQL注入或跨站脚本(XSS)攻击。本文从参数校验、预编译SQL、输入过滤、输出转义四个维度,结合MyBatis、Spring拦截器与工具类,提供可落地的安全编码方案。
1. 强制使用预编译语句(PreparedStatement)
杜绝字符串拼接SQL:
// ❌ 反例:MyBatis中使用${}导致注入
@Select("SELECT * FROM cps_order WHERE user_id = ${userId}")
List<Order> unsafeSelect(@Param("userId") String userId);
// ✅ 正确:使用#{}
@Select("SELECT * FROM cps_order WHERE user_id = #{userId}")
List<Order> safeSelect(@Param("userId") String userId);
动态表名/字段名需白名单校验:
public List<Order> queryByDynamicColumn(String column, String value) {
// 白名单校验
Set<String> allowedColumns = Set.of("user_id", "merchant_id", "order_status");
if (!allowedColumns.contains(column)) {
throw new IllegalArgumentException("Invalid column name");
}
// 使用注解+XML避免${}
return orderMapper.selectByColumn(column, value);
}
XML中安全写法:
<select id="selectByColumn" resultType="Order">
SELECT * FROM cps_order
WHERE
<choose>
<when test="column == 'user_id'">user_id = #{value}</when>
<when test="column == 'merchant_id'">merchant_id = #{value}</when>
<otherwise>1=0</otherwise>
</choose>
</select>
2. 全局参数校验(Bean Validation + 自定义注解)
对ID、手机号等字段做格式约束:
package baodanbao.com.cn.cps.security;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SafeStringValidator.class)
public @interface SafeString {
String message() default "Invalid characters";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class SafeStringValidator implements ConstraintValidator<SafeString, String> {
private static final Pattern SAFE_PATTERN = Pattern.compile("^[a-zA-Z0-9_\\-@.]+$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || SAFE_PATTERN.matcher(value).matches();
}
}
DTO使用:
public class OrderQueryDTO {
@SafeString
private String userId;
@Pattern(regexp = "^M\\d{8,12}$")
private String merchantId;
}
Controller启用校验:
@PostMapping("/orders")
public List<Order> queryOrders(@Valid @RequestBody OrderQueryDTO dto) {
return orderService.query(dto);
}

3. XSS输入过滤(HTML标签清除)
在进入业务逻辑前清洗富文本:
@Component
public class XssRequestWrapper extends HttpServletRequestWrapper {
public XssRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return cleanXss(value);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) return null;
return Arrays.stream(values).map(this::cleanXss).toArray(String[]::new);
}
private String cleanXss(String value) {
if (value == null) return null;
return Jsoup.clean(value, Safelist.none()).trim();
}
}
// 拦截器注册
@WebFilter("/*")
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
chain.doFilter(new XssRequestWrapper((HttpServletRequest) request), response);
}
}
4. 输出转义(模板与JSON序列化)
对返回前端的数据进行HTML实体编码:
// 自定义Jackson序列化器
public class XssStringSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value != null) {
String escaped = StringEscapeUtils.escapeHtml4(value);
gen.writeString(escaped);
} else {
gen.writeNull();
}
}
}
// 应用于敏感字段
public class UserInfo {
@JsonSerialize(using = XssStringSerializer.class)
private String nickname;
@JsonSerialize(using = XssStringSerializer.class)
private String remark;
}
Thymeleaf默认已转义,但需禁用th:utext:
<!-- 安全 -->
<span th:text="${user.remark}"></span>
<!-- 危险!禁止使用 -->
<span th:utext="${user.remark}"></span>
5. MyBatis拦截器防御二次注入
即使使用#{},若参数本身含恶意内容仍可能影响业务逻辑。通过拦截器统一清洗:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Component
public class SqlSafeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object parameter = invocation.getArgs()[1];
sanitizeParameter(parameter);
return invocation.proceed();
}
private void sanitizeParameter(Object param) {
if (param instanceof Map) {
((Map<?, ?>) param).values().forEach(this::sanitizeValue);
} else {
sanitizeValue(param);
}
}
private void sanitizeValue(Object value) {
if (value instanceof String) {
String str = (String) value;
// 禁止包含SQL关键字(仅辅助,不能替代预编译)
if (str.matches(".*(?i)(union|select|insert|update|delete|drop|exec|;|--).*")) {
throw new SecurityException("Potential SQL injection detected");
}
}
}
}
6. 日志脱敏防信息泄露
记录日志时自动脱敏敏感字段:
@Aspect
@Component
public class LogSanitizeAspect {
@Before("@annotation(org.slf4j.Logger)")
public void sanitizeLogArgs(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof String) {
args[i] = ((String) args[i]).replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); // 手机号
}
}
}
}
本文著作权归 俱美开放平台 ,转载请注明出处!
更多推荐




所有评论(0)