springboot安全组件总结

Posted 剑雪封喉r

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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();

以上是关于springboot安全组件总结的主要内容,如果未能解决你的问题,请参考以下文章

UE4 C++设置攻击间隔面向敌人攻击

SpringBoot 安全漏洞之SQL注入和XSS攻击

SpringBoot 安全漏洞之XSS注入攻击(jsoup版本)

汽车信息安全攻击研究实例总结

Android安全开发之启动私有组件漏洞浅谈

Android研发安全2-Activity组件安全(下)