ruoyi-admin/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}个字符之间 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); } } 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); } } ruoyi-common/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 + ")"; /** * 获取客户端IP * * @return IP地址 */ public static String getIpAddr() { return getIpAddr(ServletUtils.getRequest()); } /** * 获取客户端IP * @@ -248,7 +266,7 @@ } } } return ip; return StringUtils.substring(ip, 0, 255); } /** @@ -261,4 +279,104 @@ { 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; } } ruoyi-framework/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) ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
@@ -16,7 +16,6 @@ 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 @@ 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(); ruoyi-framework/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 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
@@ -9,16 +9,18 @@ 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 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); } ruoyi-framework/src/main/java/com/ruoyi/framework/web/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()); ruoyi-ui/src/views/monitor/logininfor/index.vue
@@ -110,7 +110,7 @@ <dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/> </template> </el-table-column> <el-table-column label="操作信息" align="center" prop="msg" /> <el-table-column label="操作信息" align="center" prop="msg" :show-overflow-tooltip="true" /> <el-table-column label="登录日期" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180"> <template slot-scope="scope"> <span>{{ parseTime(scope.row.loginTime) }}</span> sql/ry_20230221.sql
文件名从 sql/ry_20230216.sql 修改 @@ -545,6 +545,7 @@ 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黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); -- ----------------------------