diff --git a/ruoyi-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties
index b7433dcf..c3956b27 100644
--- a/ruoyi-admin/src/main/resources/i18n/messages.properties
+++ b/ruoyi-admin/src/main/resources/i18n/messages.properties
@@ -9,6 +9,7 @@ user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分
user.password.delete=对不起,您的账号已被删除
user.blocked=用户已封禁,请联系管理员
role.blocked=角色已封禁,请联系管理员
+login.blocked=很遗憾,访问IP已被列入系统黑名单
user.logout.success=退出成功
length.not.valid=长度必须在{min}到{max}个字符之间
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java
new file mode 100644
index 00000000..2bf50386
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 黑名单IP异常类
+ *
+ * @author ruoyi
+ */
+public class BlackListException extends UserException
+{
+ private static final long serialVersionUID = 1L;
+
+ public BlackListException()
+ {
+ super("login.blocked", null);
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java
new file mode 100644
index 00000000..eff81812
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 用户不存在异常类
+ *
+ * @author ruoyi
+ */
+public class UserNotExistsException extends UserException
+{
+ private static final long serialVersionUID = 1L;
+
+ public UserNotExistsException()
+ {
+ super("user.not.exists", null);
+ }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
index eecf8d65..b410fdae 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
@@ -3,6 +3,7 @@ package com.ruoyi.common.utils.ip;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
+import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
/**
@@ -12,6 +13,23 @@ import com.ruoyi.common.utils.StringUtils;
*/
public class IpUtils
{
+ public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
+ // 匹配 ip
+ public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
+ public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
+ // 匹配网段
+ public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
+
+ /**
+ * 获取客户端IP
+ *
+ * @return IP地址
+ */
+ public static String getIpAddr()
+ {
+ return getIpAddr(ServletUtils.getRequest());
+ }
+
/**
* 获取客户端IP
*
@@ -248,7 +266,7 @@ public class IpUtils
}
}
}
- return ip;
+ return StringUtils.substring(ip, 0, 255);
}
/**
@@ -261,4 +279,104 @@ public class IpUtils
{
return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}
+
+ /**
+ * 是否为IP
+ */
+ public static boolean isIP(String ip)
+ {
+ return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP);
+ }
+
+ /**
+ * 是否为IP,或 *为间隔的通配符地址
+ */
+ public static boolean isIpWildCard(String ip)
+ {
+ return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
+ }
+
+ /**
+ * 检测参数是否在ip通配符里
+ */
+ public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip)
+ {
+ String[] s1 = ipWildCard.split("\\.");
+ String[] s2 = ip.split("\\.");
+ boolean isMatchedSeg = true;
+ for (int i = 0; i < s1.length && !s1[i].equals("*"); i++)
+ {
+ if (!s1[i].equals(s2[i]))
+ {
+ isMatchedSeg = false;
+ break;
+ }
+ }
+ return isMatchedSeg;
+ }
+
+ /**
+ * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
+ */
+ public static boolean isIPSegment(String ipSeg)
+ {
+ return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
+ }
+
+ /**
+ * 判断ip是否在指定网段中
+ */
+ public static boolean ipIsInNetNoCheck(String iparea, String ip)
+ {
+ int idx = iparea.indexOf('-');
+ String[] sips = iparea.substring(0, idx).split("\\.");
+ String[] sipe = iparea.substring(idx + 1).split("\\.");
+ String[] sipt = ip.split("\\.");
+ long ips = 0L, ipe = 0L, ipt = 0L;
+ for (int i = 0; i < 4; ++i)
+ {
+ ips = ips << 8 | Integer.parseInt(sips[i]);
+ ipe = ipe << 8 | Integer.parseInt(sipe[i]);
+ ipt = ipt << 8 | Integer.parseInt(sipt[i]);
+ }
+ if (ips > ipe)
+ {
+ long t = ips;
+ ips = ipe;
+ ipe = t;
+ }
+ return ips <= ipt && ipt <= ipe;
+ }
+
+ /**
+ * 校验ip是否符合过滤串规则
+ *
+ * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
+ * @param ip 校验IP地址
+ * @return boolean 结果
+ */
+ public static boolean isMatchedIp(String filter, String ip)
+ {
+ if (StringUtils.isEmpty(filter) && StringUtils.isEmpty(ip))
+ {
+ return false;
+ }
+ String[] ips = filter.split(";");
+ for (String iStr : ips)
+ {
+ if (isIP(iStr) && iStr.equals(ip))
+ {
+ return true;
+ }
+ else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip))
+ {
+ return true;
+ }
+ else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
index 34138773..c78f5b0b 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
@@ -90,7 +90,7 @@ public class LogAspect
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
- String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+ String ip = IpUtils.getIpAddr();
operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
if (loginUser != null)
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
index f06465f3..a2015d70 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
@@ -16,7 +16,6 @@ import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.enums.LimitType;
import com.ruoyi.common.exception.ServiceException;
-import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
@@ -79,7 +78,7 @@ public class RateLimiterAspect
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP)
{
- stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
+ stringBuffer.append(IpUtils.getIpAddr()).append("-");
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java
index f9c4fcca..98517b78 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java
@@ -38,7 +38,7 @@ public class AsyncFactory
final Object... args)
{
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
- final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+ final String ip = IpUtils.getIpAddr();
return new TimerTask()
{
@Override
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
index 56669a7a..4831849d 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
@@ -9,16 +9,18 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.exception.user.BlackListException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
+import com.ruoyi.common.exception.user.UserNotExistsException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
-import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
@@ -61,12 +63,10 @@ public class SysLoginService
*/
public String login(String username, String password, String code, String uuid)
{
- boolean captchaEnabled = configService.selectCaptchaEnabled();
- // 验证码开关
- if (captchaEnabled)
- {
- validateCaptcha(username, code, uuid);
- }
+ // 验证码校验
+ validateCaptcha(username, code, uuid);
+ // 登录前置校验
+ loginPreCheck(username, password);
// 用户验证
Authentication authentication = null;
try
@@ -110,18 +110,58 @@ public class SysLoginService
*/
public void validateCaptcha(String username, String code, String uuid)
{
- String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
- String captcha = redisCache.getCacheObject(verifyKey);
- redisCache.deleteObject(verifyKey);
- if (captcha == null)
+ boolean captchaEnabled = configService.selectCaptchaEnabled();
+ if (captchaEnabled)
{
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
- throw new CaptchaExpireException();
+ String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
+ String captcha = redisCache.getCacheObject(verifyKey);
+ redisCache.deleteObject(verifyKey);
+ if (captcha == null)
+ {
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+ throw new CaptchaExpireException();
+ }
+ if (!code.equalsIgnoreCase(captcha))
+ {
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+ throw new CaptchaException();
+ }
}
- if (!code.equalsIgnoreCase(captcha))
+ }
+
+ /**
+ * 登录前置校验
+ * @param username 用户名
+ * @param password 用户密码
+ */
+ public void loginPreCheck(String username, String password)
+ {
+ // 用户名或密码为空 错误
+ if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
- throw new CaptchaException();
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
+ throw new UserNotExistsException();
+ }
+ // 密码如果不在指定范围内 错误
+ if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
+ || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
+ {
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+ throw new UserPasswordNotMatchException();
+ }
+ // 用户名不在指定范围内 错误
+ if (username.length() < UserConstants.USERNAME_MIN_LENGTH
+ || username.length() > UserConstants.USERNAME_MAX_LENGTH)
+ {
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+ throw new UserPasswordNotMatchException();
+ }
+ // IP黑名单校验
+ String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
+ if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
+ {
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
+ throw new BlackListException();
}
}
@@ -134,7 +174,7 @@ public class SysLoginService
{
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
- sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+ sysUser.setLoginIp(IpUtils.getIpAddr());
sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser);
}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
index cadeb4e3..4b190d02 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
@@ -156,7 +156,7 @@ public class TokenService
public void setUserAgent(LoginUser loginUser)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
- String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+ String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
diff --git a/ruoyi-ui/src/views/monitor/logininfor/index.vue b/ruoyi-ui/src/views/monitor/logininfor/index.vue
index 8a9b17ab..8e3dc034 100644
--- a/ruoyi-ui/src/views/monitor/logininfor/index.vue
+++ b/ruoyi-ui/src/views/monitor/logininfor/index.vue
@@ -110,7 +110,7 @@
-
+
{{ parseTime(scope.row.loginTime) }}
diff --git a/sql/ry_20230216.sql b/sql/ry_20230221.sql
similarity index 98%
rename from sql/ry_20230216.sql
rename to sql/ry_20230221.sql
index d30cfbf8..123fab22 100644
--- a/sql/ry_20230216.sql
+++ b/sql/ry_20230221.sql
@@ -545,6 +545,7 @@ insert into sys_config values(2, '用户管理-账号初始密码', 'sys
insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' );
insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)');
insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)');
+insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)');
-- ----------------------------