springboot安全组件总结

目录

防SQL注入组件

什么是sql注入

PreparedStatement防止SQL注入

mybatis防止SQL注入

防XSS攻击组件

XSS过滤处理

防止XSS攻击的过滤器

过滤器配置

防重复提交组件

自定义注解防止表单重复提交

自定义防止重复提交拦截器

对防重复提交业务,拦截器具体逻辑实现

​​​​​​使用方法一:采用默认参数

​​​​​​使用方法二:指定防重复时间和错误消息

​​​​​​前端通过js控制


防SQL注入组件

什么是sql注入

sql注入解释:是一种代码注入技术,用于攻击数据驱动的应用,恶意的SQL语句被插入到执行的实体字段中(例如,为了转储数据库内容给攻击者)攻击者在界面的表单信息或URL上输入一些奇怪的SQL片段(例如“or ‘1’=’1’”这样的语句),有可能入侵参数检验不足的应用程序。所以,在应用中需要做一些工作,来防备这样的攻击方式。在一些安全性要求很高的应用中(比如征信一代查询),经常使用将SQL语句全部替换为存储过程这样的方式,来防止SQL注入。这当然是一种很安全的方式,但平时开发中,可能不需要这种死板的方式。

PreparedStatement防止SQL注入

PreparedStatement对象防止sql注入的方式是把用户非法输入的单引号转化为双单引号,从而达到了防止sql注入的目的

以测试表financialec表为例,如下图:

Mysql5.0以上版本使用com.mysql.cj.jdbc.Driver驱动类创建连接,不再使用com.mysql.jdbc.Driver驱动类创建连接。如下图创建测试类:

最终执行的sql语句打印出来是:

select * from financialec where queryorgname='123'' or ''8''=''8'

如下图所示:

由此可见,prepareStatement对象防止sql注入的方式是把用户非法输入的单引号转化为双单引号,从而达到了防止sql注入的目的

mybatis防止SQL注入

MyBatis框架作为一款半自动化的持久层框架,其SQL语句有时需要手动编写,这个时候需要防止SQL注入。其实,MyBatis的SQL是一个具有“输入+输出”的功能,类似于函数的结构。

首先看一下下面两个sql语句的区别:

例1:

<delete id="deleteFinancialecById" parameterType="String">

        delete from financialec where id = #{id}

</delete>

例2:

<delete id="deleteFinancialecById" parameterType="String">

        delete from financialec where id = ${id}

</delete>

其中,parameterType表示了输入的参数类型。

如果我们想防止SQL注入,理所当然地要在输入参数上下功夫。上面代码中分别使用#、$即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的:

delete from financialec where id = ?

delete from financialec where id = 111

    通过测试,不管输入什么参数,使用#时打印出的SQL都是一样的。这是因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。

    mybatis中的#和$的区别:

  1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号

如:where id=#{id},如果传入的值是111,那么解析成sql时的值为where id="111"

  1. $将传入的数据直接显示生成在sql中

如:where id=${id},如果传入的值是111,那么解析成sql时的值为where id=111;

如果传入的值是;drop table financialec,则解析成的sql为:delete from financialec where id=;drop table financialec;

  1. #方式能够很大程度防止sql注入,$方式无法防止Sql注入
  2. $方式一般用于传入数据库对象,例如传入表名
  3. 一般能用#的就别用$,若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击
  4. 在MyBatis中,“${xxx}”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“${xxx}”这样的参数格式。所以,这样的参数需要在代码中手工进行处理来防止注入
  5. #{}是经过预编译的,相当于JDBC中的PreparedStatement,是安全的;${}是未 

    经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入的风险。

综合上述分析,在编写MyBatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。

防XSS攻击组件

XSS过滤处理

public class XssHttpServletRequestWrapper extends 

                                       HttpServletRequestWrapper {

/**

 * @param request

 */

public XssHttpServletRequestWrapper(HttpServletRequest request) {

super(request);

}



@Override

public String[] getParameterValues(String name) {

String[] values = super.getParameterValues(name);

if (values != null) {

int length = values.length;

String[] escapseValues = new String[length];

for (int i = 0; i < length; i++) {

// 防xss攻击和过滤前后空格

escapseValues[i] = EscapeUtil.clean(values[i]).trim();

}

return escapseValues;

}

return super.getParameterValues(name);

}



@Override

public ServletInputStream getInputStream() throws IOException {

// 非json类型,直接返回

if (!isJsonRequest()) {

return super.getInputStream();

}



// 为空,直接返回

String json = IOUtils.toString(super.getInputStream(), "utf-8");

if (StringUtils.isEmpty(json)) {

return super.getInputStream();

}



// xss过滤

json = EscapeUtil.clean(json).trim();

final ByteArrayInputStream bis = new ByteArrayInputStream(

json.getBytes("utf-8"));

return new ServletInputStream() {

@Override

public boolean isFinished() {

return true;

}



@Override

public boolean isReady() {

return true;

}



@Override

public void setReadListener(ReadListener readListener) {

}



@Override

public int read() throws IOException {

return bis.read();

}

};

}



/**

 * 是否是Json请求

 * 

 * @param request

 */

public boolean isJsonRequest() {

String header = super.getHeader(HttpHeaders.CONTENT_TYPE);

return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header)

|| MediaType.APPLICATION_JSON_UTF8_VALUE

.equalsIgnoreCase(header);

}

}

防止XSS攻击的过滤器

public class XssFilter implements Filter {

/**

 * 排除链接

 */

public List<String> excludes = new ArrayList<>();



/**

 * xss过滤开关

 */

public boolean enabled = false;



@Override

public void init(FilterConfig filterConfig) throws ServletException {

String tempExcludes = filterConfig.getInitParameter("excludes");

String tempEnabled = filterConfig.getInitParameter("enabled");

if (StringUtils.isNotEmpty(tempExcludes)) {

String[] url = tempExcludes.split(",");

for (int i = 0; url != null && i < url.length; i++) {

excludes.add(url[i]);

}

}

if (StringUtils.isNotEmpty(tempEnabled)) {

enabled = Boolean.valueOf(tempEnabled);

}

}



@Override

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;

HttpServletResponse resp = (HttpServletResponse) response;

if (handleExcludeURL(req, resp)) {

chain.doFilter(request, response);

return;

}

XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(

(HttpServletRequest) request);

chain.doFilter(xssRequest, response);

}



private boolean handleExcludeURL(HttpServletRequest request,

HttpServletResponse response) {

if (!enabled) {

return true;

}

if (excludes == null || excludes.isEmpty()) {

return false;

}

String url = request.getServletPath();

for (String pattern : excludes) {

Pattern p = Pattern.compile("^" + pattern);

Matcher m = p.matcher(url);

if (m.find()) {

return true;

}

}

return false;

}



@Override

public void destroy() {



}

}

过滤器配置

@Configuration

public class FilterConfig {

@Value("${xss.enabled}")

private String enabled;



@Value("${xss.excludes}")

private String excludes;



@Value("${xss.urlPatterns}")

private String urlPatterns;



@SuppressWarnings({ "rawtypes", "unchecked" })

@Bean

public FilterRegistrationBean xssFilterRegistration() {

FilterRegistrationBean registration = new FilterRegistrationBean();

registration.setDispatcherTypes(DispatcherType.REQUEST);

registration.setFilter(new XssFilter());

registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));

registration.setName("xssFilter");

registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);

Map<String, String> initParameters = new HashMap<String, String>();

// excludes用于配置不需要参数过滤的请求url

initParameters.put("excludes", excludes);

initParameters.put("enabled", enabled);

registration.setInitParameters(initParameters);

return registration;

}



@SuppressWarnings({ "rawtypes", "unchecked" })

@Bean

public FilterRegistrationBean someFilterRegistration() {

FilterRegistrationBean registration = new FilterRegistrationBean();

registration.setFilter(new RepeatableFilter());

registration.addUrlPatterns("/*");

registration.setName("repeatableFilter");

registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);

return registration;

}



}

防重复提交组件

自定义注解防止表单重复提交

package org.jeecg.common.RepeatSubmit;



import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Inherited;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

 * 自定义注解防止表单重复提交

 */

@Inherited

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface RepeatSubmit {

    /**

     * 间隔时间(ms),小于此时间视为重复提交

     */

    public int interval() default 5000;



    /**

     * 提示消息

     */

    public String message() default "不允许重复提交,请稍候再试";

}

注解参数说明:

参数

类型

默认值

描述

interval

int

5000

间隔时间(ms),小于此时间视为重复提交

message

String

不允许重复提交,请稍后再试

提示消息

自定义防止重复提交拦截器

package org.jeecg.common.RepeatSubmit.interceptor;

import com.alibaba.fastjson.JSONObject;
import org.jeecg.common.RepeatSubmit.RepeatSubmit;
import org.jeecg.common.api.vo.Result;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
/**
 * @version V1.0
 * @Description:防止重复提交拦截器
 * @author: qixiongfei
 * @date: 2022/2/28 10:19
 */
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, 

        HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RepeatSubmit annotation = 

                            method.getAnnotation(RepeatSubmit.class);
            if (annotation != null) {
                if (this.isRepeatSubmit(request, annotation)) {
                    Result result = Result.OK(annotation.message());
                    renderString(response, 

                                    JSONObject.toJSONString(result));
                    return false;
                }
            }
            return true;
        } else {
            return true;
        }
    }

    /**
     * 验证是否重复提交由子类实现具体的防重复提交的规则
     *
     * @param request
     * @return
     * @throws Exception
     */
    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);

    /**
     * 将字符串渲染到客户端
     *
     * @param response 渲染对象
     * @param string   待渲染的字符串
     */
    public static void renderString(HttpServletResponse response, String  

                                                  string) {
        try {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

对防重复提交业务,拦截器具体逻辑实现

package org.jeecg.common.RepeatSubmit.interceptor.impl;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.RepeatSubmit.RepeatSubmit;
import org.jeecg.common.RepeatSubmit.filter.RepeatedlyRequestWrapper;
import org.jeecg.common.RepeatSubmit.http.HttpHelper;
import org.jeecg.common.RepeatSubmit.interceptor.RepeatSubmitInterceptor;
import org.jeecg.common.RepeatSubmit.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 判断请求url和数据是否和上一次相同,如果和上次相同,则是重复提交表单。 有效时间为10秒内。
 * @author: qixiongfei
 * @date: 2022/2/28 11:41
 * @version V1.0
 */
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
    public final String REPEAT_PARAMS = "repeatParams";

    public final String REPEAT_TIME = "repeatTime";

    public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    @Autowired
    private RedisCache redisCache;

    @SuppressWarnings("unchecked")
    @Override
    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
        String nowParams = "";
        if (request instanceof RepeatedlyRequestWrapper) {
            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
        }

        // body参数为空,获取Parameter的数据
        if (StringUtils.isEmpty(nowParams)) {
            nowParams = JSONObject.toJSONString(request.getParameterMap());
        }
        Map<String, Object> nowDataMap = new HashMap<String, Object>();
        nowDataMap.put(REPEAT_PARAMS, nowParams);
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());

        // 请求地址(作为存放cache的key值)
        String url = request.getRequestURI();

        System.out.println(url);
        // 唯一值(没有消息头则使用请求地址)
        String submitKey = StringUtils.trimToEmpty(request.getHeader(header));

        // 唯一标识(指定key + url + 消息头)
        String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + submitKey;

        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
        //1、第1秒点击时走=null方法,五秒之内走!=null方法 五秒之外后的第1秒点击时redis存储的信息过期,重新开始走=null方法
        //1、第1秒点击时走=null方法,五秒之内走!=null方法 五秒之外后的第1秒点击时redis存储的信息过期,重新开始走=null方法
        if (sessionObj != null) {
            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
            if (sessionMap.containsKey(url)) {
                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) {
                    return true;
                }
            }
        }
        Map<String, Object> cacheMap = new HashMap<String, Object>();
        cacheMap.put(url, nowDataMap);
        redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);
        return false;
    }

    /**
     * 判断参数是否相同
     */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
        String preParams = (String) preMap.get(REPEAT_PARAMS);
        return nowParams.equals(preParams);
    }

    /**
     * 判断两次间隔时间
     */
    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval) {
        long time1 = (Long) nowMap.get(REPEAT_TIME);
        long time2 = (Long) preMap.get(REPEAT_TIME);
        if ((time1 - time2) < interval) {
            return true;
        }
        return false;
    }
}

​​​​​​使用方法一:采用默认参数

@AutoLog(value = "知识库-点赞")
@ApiOperation(value = "知识库-点赞", notes = "知识库-点赞")
@PutMapping(value = "/like")
@RepeatSubmit
public Result<?> like(
        @RequestParam(name = "condition", required = true) String condition) {

......

}

​​​​​​使用方法二:指定防重复时间和错误消息

@AutoLog(value = "知识库-点赞")
@ApiOperation(value = "知识库-点赞", notes = "知识库-点赞")
@PutMapping(value = "/like")
@RepeatSubmit(interval = 5000, message = "点赞不允许重复提交,请稍后再试!")
public Result<?> like(
        @RequestParam(name = "condition", required = true) String condition) {

......

}

​​​​​​前端通过js控制

// 禁用按钮

$.modal.disable();

// 启用按钮

$.modal.enable();
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值