系列文章目录



前言

群里有个小伙伴提问,SpringBoot项目怎样做参数校验,防止SQL注入?改动尽量少,高灵活,因为并不是每个参数有需要校验的。

一、本文要点

接前文,我们介绍了如何做spring拓展,轻松面对面试官的提问了。本文介绍如何自定义参数校验,保护我们的系统,避免各种参数问题。系列文章完整目录

  • springboot 自定义校验参数
  • springboot 自定义校验规则
  • springboot 注解
  • springboot 校验枚举
  • springboot 预防SQL注入

二、开发环境

  • jdk 1.8
  • maven 3.6.2
  • springboot 2.4.3
  • idea 2020

三、创建项目

1、使用早期文章快速创建项目。
《搭建大型分布式服务(十八)Maven自定义项目脚手架》

2、创建Book项目

mvn archetype:generate  -DgroupId="com.mmc.lesson" -DartifactId="book" -Dversion=1.0-SNAPSHOT -Dpackage="com.mmc.lesson" -DarchetypeArtifactId=member-archetype  -DarchetypeGroupId=com.mmc.lesson -DarchetypeVersion=1.0.0-SNAPSHOT -B

四、自定义校验

1、修改pom.xml,增加依赖(高版本的springboot需要这样引入)。

   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

2、假设系统存在这样一段拼接SQL语句的方法,很明显,我们需要对uname字段做校验,防止别人传 " 1 or 1<>2 " 或者 " 1 or 1=1 – " 这样的值从而达到SQL注入。

        if (StringUtils.hasText(req.getUname())) {
            sql += " and uname = " + req.getUname();
        }

3、定义注解@SqlInjection,作用在需要防止SQL注入的参数上。

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SqlInjectionConstraintValidator.class)
@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface SqlInjection {

    /**
     * 错误提示.
     */
    String message() default "Contains illegal characters";

    /**
     * groups.
     */
    Class<?>[] groups() default {};

    /**
     * payload.
     */
    Class<? extends Payload>[] payload() default {};
}

4、编写SqlInjectionConstraintValidator.java,使用正则表达式对特殊字符做校验。

@Slf4j
public class SqlInjectionConstraintValidator implements ConstraintValidator<SqlInjection, String> {

    /**
     * 预编译SQL过滤正则表达式
     */
    private Pattern sqlPattern = Pattern.compile(
            "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|update|and|or|delete"
                    + "|insert|trancate|char|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)");

    @Override
    public void initialize(SqlInjection constraintAnnotation) {

        log.info("initialize constraint");

    }

    @Override
    public boolean isValid(String source, ConstraintValidatorContext constraintValidatorContext) {

        if (StringUtils.hasText(source)) {

            Matcher matcher = sqlPattern.matcher(source);
            if (matcher.find()) {
                log.error("包含非法字符|{}", source);
                return false;
            }
        }

        return true;
    }
}

5、增加@Validated注解,使我们自定义校验规则生效。

    @GetMapping("/get")
    public Result get(@Validated GetBookReq req) {

        System.out.println("IndexController.get");

        TblMemberInfo book = new TblMemberInfo();
        book.setUid(8888L);

        TblMemberInfo ret = bookService.get(book);
        return Result.ok(ret);
    }

五、测试一下

1、编写单元测试

@Slf4j
@ActiveProfiles("dev")
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class IndexControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testNormal() throws Exception {


        this.mockMvc.perform(MockMvcRequestBuilders.get("/api/get")
                .param("uname", "zhangsan") // 正常传惨
                .characterEncoding("UTF-8")
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.code", IsEqual.equalTo(0)));

    }

    @Test
    void testError() throws Exception {

        this.mockMvc.perform(MockMvcRequestBuilders.get("/api/get")
                .param("uname", "1 and 1<>1 -- ") // SQL 注入
                .characterEncoding("UTF-8")
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.code", IsEqual.equalTo(-1)));

    }
}

2、测试通过。

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'getBookReq' on field 'uname': rejected value [1 and 1<>1 -- ]; codes [SqlInjection.getBookReq.uname,SqlInjection.uname,SqlInjection.java.lang.String,SqlInjection]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [getBookReq.uname,uname]; arguments []; default message [uname]]; default message [Contains illegal characters]
	at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:170)
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
	at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)

六、小结

至此,我们就简单实现了自定义参数校验功能啦。课后作业,小伙伴可以自行去实现枚举参数校验哦。下一篇《搭建大型分布式服务(二十一)Mybatis 如何打印SQL语句和执行时间?

加我加群一起交流学习!更多干货下载和大厂内推等着你

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐