From 3ef9c108c9e90a01aee36b708983dcb42175c429 Mon Sep 17 00:00:00 2001 From: RuoYi <yzz_ivy@163.com> Date: 星期二, 21 二月 2023 09:01:42 +0800 Subject: [PATCH] 支持登录IP黑名单限制 --- src/main/resources/i18n/messages.properties | 1 src/main/java/com/ruoyi/common/utils/ip/IpUtils.java | 120 +++++++++++++++++++++++++++++ src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java | 2 sql/ry_20230221.sql | 1 src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java | 16 ++++ src/main/java/com/ruoyi/framework/security/service/SysLoginService.java | 76 ++++++++++++++---- src/main/java/com/ruoyi/framework/aspectj/LogAspect.java | 2 src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java | 3 src/main/java/com/ruoyi/common/exception/user/BlackListException.java | 16 ++++ src/main/java/com/ruoyi/framework/security/service/TokenService.java | 2 10 files changed, 215 insertions(+), 24 deletions(-) diff --git a/sql/ry_20230216.sql b/sql/ry_20230221.sql similarity index 99% rename from sql/ry_20230216.sql rename to sql/ry_20230221.sql index d30cfbf..123fab2 100644 --- a/sql/ry_20230216.sql +++ b/sql/ry_20230221.sql @@ -545,6 +545,7 @@ insert into sys_config values(3, '涓绘鏋堕〉-渚ц竟鏍忎富棰�', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '娣辫壊涓婚theme-dark锛屾祬鑹蹭富棰榯heme-light' ); insert into sys_config values(4, '璐﹀彿鑷姪-楠岃瘉鐮佸紑鍏�', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '鏄惁寮�鍚獙璇佺爜鍔熻兘锛坱rue寮�鍚紝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榛戝悕鍗曢檺鍒讹紝澶氫釜鍖归厤椤逛互;鍒嗛殧锛屾敮鎸佸尮閰嶏紙*閫氶厤銆佺綉娈碉級'); -- ---------------------------- diff --git a/src/main/java/com/ruoyi/common/exception/user/BlackListException.java b/src/main/java/com/ruoyi/common/exception/user/BlackListException.java new file mode 100644 index 0000000..2bf5038 --- /dev/null +++ b/src/main/java/com/ruoyi/common/exception/user/BlackListException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 榛戝悕鍗旾P寮傚父绫� + * + * @author ruoyi + */ +public class BlackListException extends UserException +{ + private static final long serialVersionUID = 1L; + + public BlackListException() + { + super("login.blocked", null); + } +} diff --git a/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java b/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java new file mode 100644 index 0000000..eff8181 --- /dev/null +++ b/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/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java b/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java index eecf8d6..b410fda 100644 --- a/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java +++ b/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java @@ -3,6 +3,7 @@ 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 @@ */ 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 + ")"; + + /** + * 鑾峰彇瀹㈡埛绔疘P + * + * @return IP鍦板潃 + */ + public static String getIpAddr() + { + return getIpAddr(ServletUtils.getRequest()); + } + /** * 鑾峰彇瀹㈡埛绔疘P * @@ -248,7 +266,7 @@ } } } - return ip; + return StringUtils.substring(ip, 0, 255); } /** @@ -261,4 +279,104 @@ { return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } + + /** + * 鏄惁涓篒P + */ + public static boolean isIP(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP); + } + + /** + * 鏄惁涓篒P锛屾垨 *涓洪棿闅旂殑閫氶厤绗﹀湴鍧� + */ + 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/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java index 88e2fa7..6cd38d8 100644 --- a/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java +++ b/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -90,7 +90,7 @@ 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/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java b/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java index 1f47d7a..278bcc4 100644 --- a/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java +++ b/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java @@ -14,7 +14,6 @@ import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component; 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; import com.ruoyi.framework.aspectj.lang.annotation.RateLimiter; @@ -79,7 +78,7 @@ 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/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java index cee6db9..49c907e 100644 --- a/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java +++ b/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java @@ -38,7 +38,7 @@ 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/src/main/java/com/ruoyi/framework/security/service/SysLoginService.java b/src/main/java/com/ruoyi/framework/security/service/SysLoginService.java index 9b7380e..3ecb09b 100644 --- a/src/main/java/com/ruoyi/framework/security/service/SysLoginService.java +++ b/src/main/java/com/ruoyi/framework/security/service/SysLoginService.java @@ -9,13 +9,15 @@ 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.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; @@ -46,7 +48,7 @@ @Autowired private ISysUserService userService; - + @Autowired private ISysConfigService configService; @@ -61,12 +63,10 @@ */ 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 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 @@ { 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/src/main/java/com/ruoyi/framework/security/service/TokenService.java b/src/main/java/com/ruoyi/framework/security/service/TokenService.java index 2b1c74c..4eeee56 100644 --- a/src/main/java/com/ruoyi/framework/security/service/TokenService.java +++ b/src/main/java/com/ruoyi/framework/security/service/TokenService.java @@ -156,7 +156,7 @@ 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/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index b7433dc..c3956b2 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -9,6 +9,7 @@ user.password.delete=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿宸茶鍒犻櫎 user.blocked=鐢ㄦ埛宸插皝绂侊紝璇疯仈绯荤鐞嗗憳 role.blocked=瑙掕壊宸插皝绂侊紝璇疯仈绯荤鐞嗗憳 +login.blocked=寰堥仐鎲撅紝璁块棶IP宸茶鍒楀叆绯荤粺榛戝悕鍗� user.logout.success=閫�鍑烘垚鍔� length.not.valid=闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂� -- Gitblit v1.9.3