SpringBoot使用mica-xss防止Xss攻击
Posted 龙域、白泽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot使用mica-xss防止Xss攻击相关的知识,希望对你有一定的参考价值。
Xss攻击介绍
XSS攻击又称 跨站脚本攻击,通常指利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。通俗的讲就是通过web应用可输入参数,输入script脚本实现xss攻击。主要防御措施是通过web页面关键字特殊字符过滤。
XSS攻击的原理:攻击者会在web页面中插入一些恶意的script
代码。当用户浏览该页面的时候,那么嵌套在该页面的代码就会执行,因此会达到攻击用户的目的。那根据这个原理,实际上如果没有做任何的限制,有心人就可以为所欲为了。可以在里面嵌入一些关键代码,把你的信息拿走。确实是个很严重的问题。
需求概述
在常见的开发中需要注意xss跨站脚本的攻击,需要对参数关键字做一些校验与过滤,避免将script脚本存入数据库或者造成数据泄漏等安全问题。
实例
在前端form表单的输入框中,用户没有正常输入,而是输入了一段代码:
</input><img src=1 onerror=alert1>
这个正常保存没有问题。问题出在了列表查询的时候,上面的代码就生效了,由于图片的地址乱写的,所以这个alert就起作用了来看图。
XSS攻击分类
- 反射型 XSS:通过将XSS攻击代码放在请求URL上,将其作为输入提交到服务器端。当服务器端解析提交后,XSS代码会随着响应内容一起传回浏览器,最后浏览器解析并执行XSS代码。由于整个过程像一个反射,因此称为反射型XSS。如发起如下请求:https://www.域名.com/index.php?xss=<script>alter(xss攻击)</script>。
- 存储型 XSS:与反射型XSS“相似”,但不同的是提交的XSS代码会被存储在服务器端,当下一次请求该页面时,不用提交XSS代码,也会触发XSS攻击。例如当进行用户注册时,用户提交一条包含XSS代码的注册信息到服务器端,当用户查看个人信息时候,那些个人信息就会被浏览器当成正常的html和JS解析执行,从而触发了XSS攻击。
- DOM XSS:与反射型和存储型XSS不同之处在于,DOM XSS不需要服务器的参与,通过浏览器的DOM解析即可触发XSS攻击,避免了服务器端的信息检验和字符过滤。如发起如下请求:https://www.域名.com/index.html#alert(xss攻击)
mica-xss
介绍
Mica,Spring Cloud 微服务开发核心包,支持 web 和 webflux。
mica-xss组件说明:
- 对表单绑定的字符串类型进行 xss 处理。
- 对 json 字符串数据进行 xss 处理。
- 提供路由和控制器方法级别的放行规则。
导入依赖
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-core</artifactId>
<version>2.0.9-GA</version>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-xss</artifactId>
<version>2.0.9-GA</version>
</dependency>
mica-xss 配置
@XssCleanIgnore
添加注解@XssCleanIgnore跳过XSS过滤,该注解可作用于类上和方法上
import net.dreamlu.mica.xss.core.XssCleanIgnore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@XssCleanIgnore //设置该注解 用于跳过配置的Xss 防护
@RequestMapping("/")
public class IndexController
@GetMapping("/xss")
public String xssGet(String data)
System.out.println(data);
return data;
测试
参数为:<script>alert(666)</script>
设置XSS防护
去掉注解 @XssCleanIgnore ,则data的值为空字符串
springboot安全组件总结
目录
防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中的#和$的区别:
- #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号
如:where id=#id,如果传入的值是111,那么解析成sql时的值为where id="111"
- $将传入的数据直接显示生成在sql中
如:where id=$id,如果传入的值是111,那么解析成sql时的值为where id=111;
如果传入的值是;drop table financialec,则解析成的sql为:delete from financialec where id=;drop table financialec;
- #方式能够很大程度防止sql注入,$方式无法防止Sql注入
- $方式一般用于传入数据库对象,例如传入表名
- 一般能用#的就别用$,若不得不使用“$xxx”这样的参数,要手工地做好过滤工作,来防止sql注入攻击
- 在MyBatis中,“$xxx”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“$xxx”这样的参数格式。所以,这样的参数需要在代码中手工进行处理来防止注入
- #是经过预编译的,相当于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使用mica-xss防止Xss攻击的主要内容,如果未能解决你的问题,请参考以下文章