diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
index 10da47fb4..7e9347ee8 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
@@ -3,6 +3,7 @@ package com.ruoyi.web.controller.system;
import java.util.Date;
import java.util.List;
import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@@ -45,7 +46,7 @@ public class SysIndexController extends BaseController
// 系统首页
@GetMapping("/index")
- public String index(ModelMap mmap)
+ public String index(ModelMap mmap, HttpServletRequest request)
{
// 取身份信息
SysUser user = getSysUser();
@@ -82,6 +83,8 @@ public class SysIndexController extends BaseController
}
}
String webIndex = "topnav".equalsIgnoreCase(indexStyle) ? "index-topnav" : "index";
+ // CSRF Token
+ request.getSession().setAttribute(ShiroConstants.CSRF_TOKEN, ServletUtils.generateToken());
return webIndex;
}
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index ffb65e8bd..44b90f2af 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -136,6 +136,13 @@ xss:
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
+# 防止csrf攻击
+csrf:
+ # 过滤开关
+ enabled: true
+ # 白名单(多个用逗号分隔)
+ whites:
+
# Swagger配置
swagger:
# 是否开启swagger
diff --git a/ruoyi-admin/src/main/resources/static/ruoyi/js/common.js b/ruoyi-admin/src/main/resources/static/ruoyi/js/common.js
index e55151458..e8acf3d4e 100644
--- a/ruoyi-admin/src/main/resources/static/ruoyi/js/common.js
+++ b/ruoyi-admin/src/main/resources/static/ruoyi/js/common.js
@@ -573,6 +573,12 @@ function _stopIt(e) {
/** 设置全局ajax处理 */
$.ajaxSetup({
+ beforeSend: function (xhr, settings) {
+ var csrftoken = $('meta[name=csrf-token]').attr('content')
+ if (($.common.equalsIgnoreCase(settings.type, "POST"))) {
+ xhr.setRequestHeader("csrf_token", csrftoken)
+ }
+ },
complete: function(XMLHttpRequest, textStatus) {
if (textStatus == 'timeout') {
$.modal.alertWarning("服务器超时,请稍后再试!");
diff --git a/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js b/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js
index 0784ff31d..76171899c 100644
--- a/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js
+++ b/ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js
@@ -277,6 +277,7 @@ var table = {
} else if ($.common.equals("open", target)) {
top.layer.alert(input.val(), {
title: "信息内容",
+ area: ['400px', ''],
shadeClose: true,
btn: ['确认'],
btnclass: ['btn btn-primary'],
@@ -1049,7 +1050,11 @@ var table = {
type: type,
dataType: dataType,
data: data,
- beforeSend: function () {
+ beforeSend: function (xhr, settings) {
+ var csrftoken = $('meta[name=csrf-token]').attr('content');
+ if ($.common.equalsIgnoreCase(settings.type, "POST")) {
+ xhr.setRequestHeader("csrf_token", csrftoken);
+ }
$.modal.loading("正在处理中,请稍候...");
},
success: function(result) {
@@ -1229,7 +1234,11 @@ var table = {
type: "post",
dataType: "json",
data: data,
- beforeSend: function () {
+ beforeSend: function (xhr, settings) {
+ var csrftoken = $('meta[name=csrf-token]').attr('content');
+ if (($.common.equalsIgnoreCase(settings.type, "POST"))) {
+ xhr.setRequestHeader("csrf_token", csrftoken);
+ }
$.modal.loading("正在处理中,请稍候...");
$.modal.disable();
},
@@ -1249,7 +1258,11 @@ var table = {
type: "post",
dataType: "json",
data: data,
- beforeSend: function () {
+ beforeSend: function (xhr, settings) {
+ var csrftoken = $('meta[name=csrf-token]').attr('content');
+ if (($.common.equalsIgnoreCase(settings.type, "POST"))) {
+ xhr.setRequestHeader("csrf_token", csrftoken);
+ }
$.modal.loading("正在处理中,请稍候...");
},
success: function(result) {
@@ -1275,7 +1288,11 @@ var table = {
type: "post",
dataType: "json",
data: data,
- beforeSend: function () {
+ beforeSend: function (xhr, settings) {
+ var csrftoken = $('meta[name=csrf-token]').attr('content');
+ if (($.common.equalsIgnoreCase(settings.type, "POST"))) {
+ xhr.setRequestHeader("csrf_token", csrftoken);
+ }
$.modal.loading("正在处理中,请稍候...");
},
success: function(result) {
diff --git a/ruoyi-admin/src/main/resources/templates/include.html b/ruoyi-admin/src/main/resources/templates/include.html
index d156314c1..464065878 100644
--- a/ruoyi-admin/src/main/resources/templates/include.html
+++ b/ruoyi-admin/src/main/resources/templates/include.html
@@ -5,6 +5,7 @@
+
diff --git a/ruoyi-admin/src/main/resources/templates/lock.html b/ruoyi-admin/src/main/resources/templates/lock.html
index ac89d9252..122fc33af 100644
--- a/ruoyi-admin/src/main/resources/templates/lock.html
+++ b/ruoyi-admin/src/main/resources/templates/lock.html
@@ -3,6 +3,7 @@
+
锁定屏幕
@@ -94,7 +95,9 @@
type: "post",
dataType: "json",
data: { password: password },
- beforeSend: function() {
+ beforeSend: function(xhr) {
+ var csrftoken = $('meta[name=csrf-token]').attr('content');
+ xhr.setRequestHeader("csrf_token", csrftoken);
index = layer.load(2, {shade: false});
},
success: function(result) {
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java
index 239d36fc7..acbbedab9 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java
@@ -33,9 +33,9 @@ public class ShiroConstants
public static final String ERROR = "errorMsg";
/**
- * 编码格式
+ * csrf key
*/
- public static final String ENCODING = "UTF-8";
+ public static final String CSRF_TOKEN = "csrf_token";
/**
* 当前在线会话
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java
index 6214f5643..684d969c5 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java
@@ -4,6 +4,8 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
+import java.security.SecureRandom;
+import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@@ -25,6 +27,8 @@ public class ServletUtils
*/
private final static String[] agent = { "Android", "iPhone", "iPod", "iPad", "Windows Phone", "MQQBrowser" };
+ private static final SecureRandom secureRandom = new SecureRandom();
+
/**
* 获取String参数
*/
@@ -213,4 +217,16 @@ public class ServletUtils
return StringUtils.EMPTY;
}
}
+
+ /**
+ * 生成CSRF Token
+ *
+ * @return 解码后的内容
+ */
+ public static String generateToken()
+ {
+ byte[] bytes = new byte[32];
+ secureRandom.nextBytes(bytes);
+ return Base64.getEncoder().encodeToString(bytes);
+ }
}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
index 2dfa412f1..f2598932e 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
@@ -357,6 +357,18 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return new HashSet(str2List(str, sep, true, false));
}
+ /**
+ * 字符串转list
+ *
+ * @param str 字符串
+ * @param sep 分隔符
+ * @return list集合
+ */
+ public static final List str2List(String str, String sep)
+ {
+ return str2List(str, sep, true, false);
+ }
+
/**
* 字符串转list
*
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java
index 809027036..8437f88c6 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java
@@ -33,6 +33,7 @@ import com.ruoyi.framework.shiro.session.OnlineSessionFactory;
import com.ruoyi.framework.shiro.web.CustomShiroFilterFactoryBean;
import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
+import com.ruoyi.framework.shiro.web.filter.csrf.CsrfValidateFilter;
import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter;
import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter;
import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter;
@@ -132,6 +133,18 @@ public class ShiroConfig
@Value("${shiro.rememberMe.enabled: false}")
private boolean rememberMe;
+ /**
+ * 是否开启csrf
+ */
+ @Value("${csrf.enabled: false}")
+ private boolean csrfEnabled;
+
+ /**
+ * csrf白名单链接
+ */
+ @Value("${csrf.whites: ''}")
+ private String csrfWhites;
+
/**
* 缓存管理器 使用Ehcache实现
*/
@@ -263,6 +276,17 @@ public class ShiroConfig
return logoutFilter;
}
+ /**
+ * csrf过滤器
+ */
+ public CsrfValidateFilter csrfValidateFilter()
+ {
+ CsrfValidateFilter csrfValidateFilter = new CsrfValidateFilter();
+ csrfValidateFilter.setEnabled(csrfEnabled);
+ csrfValidateFilter.setCsrfWhites(StringUtils.str2List(csrfWhites, ","));
+ return csrfValidateFilter;
+ }
+
/**
* Shiro过滤器配置
*/
@@ -309,13 +333,14 @@ public class ShiroConfig
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
filters.put("captchaValidate", captchaValidateFilter());
+ filters.put("csrfValidateFilter", csrfValidateFilter());
filters.put("kickout", kickoutSessionFilter());
// 注销成功,则跳转到指定页面
filters.put("logout", logoutFilter());
shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证
- filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
+ filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession,csrfValidateFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/csrf/CsrfValidateFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/csrf/CsrfValidateFilter.java
new file mode 100644
index 000000000..978e6639e
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/csrf/CsrfValidateFilter.java
@@ -0,0 +1,76 @@
+package com.ruoyi.framework.shiro.web.filter.csrf;
+
+import java.util.List;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.shiro.web.filter.AccessControlFilter;
+import com.ruoyi.common.constant.ShiroConstants;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.ShiroUtils;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * csrf过滤器
+ *
+ * @author ruoyi
+ */
+public class CsrfValidateFilter extends AccessControlFilter
+{
+ /**
+ * 白名单链接
+ */
+ private List csrfWhites;
+
+ @Override
+ protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
+ throws Exception
+ {
+ HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ if (!isAllowMethod(httpServletRequest))
+ {
+ return true;
+ }
+ if (StringUtils.matches(httpServletRequest.getServletPath(), csrfWhites))
+ {
+ return true;
+ }
+ return validateResponse(httpServletRequest, httpServletRequest.getHeader(ShiroConstants.CSRF_TOKEN));
+ }
+
+ public boolean validateResponse(HttpServletRequest request, String requestToken)
+ {
+ Object obj = ShiroUtils.getSession().getAttribute(ShiroConstants.CSRF_TOKEN);
+ String sessionToken = Convert.toStr(obj, "");
+ if (StringUtils.isEmpty(requestToken) || !requestToken.equalsIgnoreCase(sessionToken))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
+ {
+ ServletUtils.renderString((HttpServletResponse) response, "{\"code\":\"1\",\"msg\":\"当前请求的安全验证未通过,请刷新页面后重试。\"}");
+ return false;
+ }
+
+ private boolean isAllowMethod(HttpServletRequest request)
+ {
+ String method = request.getMethod();
+ return "POST".equalsIgnoreCase(method);
+ }
+
+ public List getCsrfWhites()
+ {
+ return csrfWhites;
+ }
+
+ public void setCsrfWhites(List csrfWhites)
+ {
+ this.csrfWhites = csrfWhites;
+ }
+}