Sfoglia il codice sorgente

支持登录IP黑名单限制

RuoYi 3 anni fa
parent
commit
cd5556d188

+ 14 - 0
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java

@@ -2,13 +2,17 @@ package com.ruoyi.auth.service;
2 2
 
3 3
 import org.springframework.beans.factory.annotation.Autowired;
4 4
 import org.springframework.stereotype.Component;
5
+import com.ruoyi.common.core.constant.CacheConstants;
5 6
 import com.ruoyi.common.core.constant.Constants;
6 7
 import com.ruoyi.common.core.constant.SecurityConstants;
7 8
 import com.ruoyi.common.core.constant.UserConstants;
8 9
 import com.ruoyi.common.core.domain.R;
9 10
 import com.ruoyi.common.core.enums.UserStatus;
10 11
 import com.ruoyi.common.core.exception.ServiceException;
12
+import com.ruoyi.common.core.text.Convert;
11 13
 import com.ruoyi.common.core.utils.StringUtils;
14
+import com.ruoyi.common.core.utils.ip.IpUtils;
15
+import com.ruoyi.common.redis.service.RedisService;
12 16
 import com.ruoyi.common.security.utils.SecurityUtils;
13 17
 import com.ruoyi.system.api.RemoteUserService;
14 18
 import com.ruoyi.system.api.domain.SysUser;
@@ -31,6 +35,9 @@ public class SysLoginService
31 35
     @Autowired
32 36
     private SysRecordLogService recordLogService;
33 37
 
38
+    @Autowired
39
+    private RedisService redisService;
40
+
34 41
     /**
35 42
      * 登录
36 43
      */
@@ -56,6 +63,13 @@ public class SysLoginService
56 63
             recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
57 64
             throw new ServiceException("用户名不在指定范围");
58 65
         }
66
+        // IP黑名单校验
67
+        String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
68
+        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
69
+        {
70
+            recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单");
71
+            throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");
72
+        }
59 73
         // 查询用户信息
60 74
         R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
61 75
 

+ 1 - 2
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysRecordLogService.java

@@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Autowired;
4 4
 import org.springframework.stereotype.Component;
5 5
 import com.ruoyi.common.core.constant.Constants;
6 6
 import com.ruoyi.common.core.constant.SecurityConstants;
7
-import com.ruoyi.common.core.utils.ServletUtils;
8 7
 import com.ruoyi.common.core.utils.StringUtils;
9 8
 import com.ruoyi.common.core.utils.ip.IpUtils;
10 9
 import com.ruoyi.system.api.RemoteLogService;
@@ -33,7 +32,7 @@ public class SysRecordLogService
33 32
     {
34 33
         SysLogininfor logininfor = new SysLogininfor();
35 34
         logininfor.setUserName(username);
36
-        logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
35
+        logininfor.setIpaddr(IpUtils.getIpAddr());
37 36
         logininfor.setMsg(message);
38 37
         // 日志状态
39 38
         if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))

+ 5 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java

@@ -51,4 +51,9 @@ public class CacheConstants
51 51
      * 登录账户密码错误次数 redis key
52 52
      */
53 53
     public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
54
+
55
+    /**
56
+     * 登录IP黑名单 cache key
57
+     */
58
+    public static final String SYS_LOGIN_BLACKIPLIST = SYS_CONFIG_KEY + "sys.login.blackIPList";
54 59
 }

+ 119 - 1
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ip/IpUtils.java

@@ -3,6 +3,7 @@ package com.ruoyi.common.core.utils.ip;
3 3
 import java.net.InetAddress;
4 4
 import java.net.UnknownHostException;
5 5
 import javax.servlet.http.HttpServletRequest;
6
+import com.ruoyi.common.core.utils.ServletUtils;
6 7
 import com.ruoyi.common.core.utils.StringUtils;
7 8
 
8 9
 /**
@@ -12,6 +13,23 @@ import com.ruoyi.common.core.utils.StringUtils;
12 13
  */
13 14
 public class IpUtils
14 15
 {
16
+    public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
17
+    // 匹配 ip
18
+    public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
19
+    public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
20
+    // 匹配网段
21
+    public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
22
+
23
+    /**
24
+     * 获取客户端IP
25
+     * 
26
+     * @return IP地址
27
+     */
28
+    public static String getIpAddr()
29
+    {
30
+        return getIpAddr(ServletUtils.getRequest());
31
+    }
32
+
15 33
     /**
16 34
      * 获取客户端IP
17 35
      * 
@@ -248,7 +266,7 @@ public class IpUtils
248 266
                 }
249 267
             }
250 268
         }
251
-        return ip;
269
+        return StringUtils.substring(ip, 0, 255);
252 270
     }
253 271
 
254 272
     /**
@@ -261,4 +279,104 @@ public class IpUtils
261 279
     {
262 280
         return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
263 281
     }
282
+
283
+    /**
284
+     * 是否为IP
285
+     */
286
+    public static boolean isIP(String ip)
287
+    {
288
+        return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP);
289
+    }
290
+
291
+    /**
292
+     * 是否为IP,或 *为间隔的通配符地址
293
+     */
294
+    public static boolean isIpWildCard(String ip)
295
+    {
296
+        return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
297
+    }
298
+
299
+    /**
300
+     * 检测参数是否在ip通配符里
301
+     */
302
+    public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip)
303
+    {
304
+        String[] s1 = ipWildCard.split("\\.");
305
+        String[] s2 = ip.split("\\.");
306
+        boolean isMatchedSeg = true;
307
+        for (int i = 0; i < s1.length && !s1[i].equals("*"); i++)
308
+        {
309
+            if (!s1[i].equals(s2[i]))
310
+            {
311
+                isMatchedSeg = false;
312
+                break;
313
+            }
314
+        }
315
+        return isMatchedSeg;
316
+    }
317
+
318
+    /**
319
+     * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
320
+     */
321
+    public static boolean isIPSegment(String ipSeg)
322
+    {
323
+        return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
324
+    }
325
+
326
+    /**
327
+     * 判断ip是否在指定网段中
328
+     */
329
+    public static boolean ipIsInNetNoCheck(String iparea, String ip)
330
+    {
331
+        int idx = iparea.indexOf('-');
332
+        String[] sips = iparea.substring(0, idx).split("\\.");
333
+        String[] sipe = iparea.substring(idx + 1).split("\\.");
334
+        String[] sipt = ip.split("\\.");
335
+        long ips = 0L, ipe = 0L, ipt = 0L;
336
+        for (int i = 0; i < 4; ++i)
337
+        {
338
+            ips = ips << 8 | Integer.parseInt(sips[i]);
339
+            ipe = ipe << 8 | Integer.parseInt(sipe[i]);
340
+            ipt = ipt << 8 | Integer.parseInt(sipt[i]);
341
+        }
342
+        if (ips > ipe)
343
+        {
344
+            long t = ips;
345
+            ips = ipe;
346
+            ipe = t;
347
+        }
348
+        return ips <= ipt && ipt <= ipe;
349
+    }
350
+
351
+    /**
352
+     * 校验ip是否符合过滤串规则
353
+     * 
354
+     * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
355
+     * @param ip 校验IP地址
356
+     * @return boolean 结果
357
+     */
358
+    public static boolean isMatchedIp(String filter, String ip)
359
+    {
360
+        if (StringUtils.isEmpty(filter) && StringUtils.isEmpty(ip))
361
+        {
362
+            return false;
363
+        }
364
+        String[] ips = filter.split(";");
365
+        for (String iStr : ips)
366
+        {
367
+            if (isIP(iStr) && iStr.equals(ip))
368
+            {
369
+                return true;
370
+            }
371
+            else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip))
372
+            {
373
+                return true;
374
+            }
375
+            else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip))
376
+            {
377
+                return true;
378
+            }
379
+        }
380
+        return false;
381
+    }
264 382
 }

+ 1 - 1
ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java

@@ -89,7 +89,7 @@ public class LogAspect
89 89
             SysOperLog operLog = new SysOperLog();
90 90
             operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
91 91
             // 请求的地址
92
-            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
92
+            String ip = IpUtils.getIpAddr();
93 93
             operLog.setOperIp(ip);
94 94
             operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
95 95
             String username = SecurityUtils.getUsername();

+ 1 - 1
ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/feign/FeignRequestInterceptor.java

@@ -48,7 +48,7 @@ public class FeignRequestInterceptor implements RequestInterceptor
48 48
             }
49 49
 
50 50
             // 配置客户端IP
51
-            requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr(ServletUtils.getRequest()));
51
+            requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr());
52 52
         }
53 53
     }
54 54
 }

+ 1 - 1
ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java

@@ -49,7 +49,7 @@ public class TokenService
49 49
         loginUser.setToken(token);
50 50
         loginUser.setUserid(userId);
51 51
         loginUser.setUsername(userName);
52
-        loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
52
+        loginUser.setIpaddr(IpUtils.getIpAddr());
53 53
         refreshToken(loginUser);
54 54
 
55 55
         // Jwt存储信息

+ 1 - 1
ruoyi-ui/src/views/system/config/index.vue

@@ -107,7 +107,7 @@
107 107
       <el-table-column label="参数主键" align="center" prop="configId" />
108 108
       <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
109 109
       <el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
110
-      <el-table-column label="参数键值" align="center" prop="configValue" />
110
+      <el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
111 111
       <el-table-column label="系统内置" align="center" prop="configType">
112 112
         <template slot-scope="scope">
113 113
           <dict-tag :options="dict.type.sys_yes_no" :value="scope.row.configType"/>

+ 1 - 1
ruoyi-ui/src/views/system/logininfor/index.vue

@@ -107,7 +107,7 @@
107 107
           <dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/>
108 108
         </template>
109 109
       </el-table-column>
110
-      <el-table-column label="描述" align="center" prop="msg" />
110
+      <el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
111 111
       <el-table-column label="访问时间" align="center" prop="accessTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
112 112
         <template slot-scope="scope">
113 113
           <span>{{ parseTime(scope.row.accessTime) }}</span>

+ 1 - 0
sql/ry_20230216.sql

@@ -544,6 +544,7 @@ insert into sys_config values(1, '主框架页-默认皮肤样式名称',     's
544 544
 insert into sys_config values(2, '用户管理-账号初始密码',         'sys.user.initPassword',    '123456',        'Y', 'admin', sysdate(), '', null, '初始化密码 123456' );
545 545
 insert into sys_config values(3, '主框架页-侧边栏主题',           'sys.index.sideTheme',      'theme-dark',    'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' );
546 546
 insert into sys_config values(4, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false',         'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)');
547
+insert into sys_config values(5, '用户登录-黑名单列表',           'sys.login.blackIPList',    '',              'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)');
547 548
 
548 549
 
549 550
 -- ----------------------------