Browse Source

修改非法退出导致用户会话占用

xusl 2 năm trước cách đây
mục cha
commit
883f599331

+ 4 - 2
backend/src/main/java/com/jiayue/ssi/constant/LoginConstants.java

@@ -1,5 +1,7 @@
 package com.jiayue.ssi.constant;
 
+import com.jiayue.ssi.dto.UserVisitInfoDto;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -19,7 +21,7 @@ public class LoginConstants {
     public static final Long LOGIN_LOCK = 100000L;
 
     /**
-     * 并发会话存储
+     * 并发会话存储<用户名,访问信息>
      */
-    public static ConcurrentMap<String, String> sessionMap = new ConcurrentHashMap<>();
+    public static ConcurrentMap<String, UserVisitInfoDto> sessionMap = new ConcurrentHashMap<>();
 }

+ 25 - 0
backend/src/main/java/com/jiayue/ssi/dto/UserVisitInfoDto.java

@@ -0,0 +1,25 @@
+package com.jiayue.ssi.dto;
+
+import lombok.Data;
+
+/**
+* 用户访问信息
+*
+* @author xsl
+* @since 2023/06/28
+*/
+@Data
+public class UserVisitInfoDto {
+    /** 账号 */
+    private String username;
+    /** 登录ip */
+    private String ip;
+    /** 访问时间 */
+    private Long vtime;
+    /** 登录地点 */
+    private String loginLocation;
+    /** 客户端浏览器 */
+    private String browser;
+    /** 客户端操作系统 */
+    private String os;
+}

+ 33 - 0
backend/src/main/java/com/jiayue/ssi/factory/LoginFactory.java

@@ -1,6 +1,7 @@
 package com.jiayue.ssi.factory;
 
 import com.jiayue.ssi.constant.Constants;
+import com.jiayue.ssi.dto.UserVisitInfoDto;
 import com.jiayue.ssi.entity.SysLogininfor;
 import com.jiayue.ssi.service.SysLogininforService;
 import com.jiayue.ssi.util.*;
@@ -61,6 +62,38 @@ public class LoginFactory {
         SpringUtils.getBean(SysLogininforService.class).insertLogininfor(logininfor);
     }
 
+    /**
+     * 记录登录信息
+     *
+     * @param userVisitInfoDto 用户名
+     * @param status 状态
+     * @param message 消息
+     * @return 任务task
+     */
+    public static void autoRecordLogininfor(UserVisitInfoDto userVisitInfoDto, final String status, final String message) {
+        // 封装对象
+        SysLogininfor logininfor = new SysLogininfor();
+        logininfor.setUserName(userVisitInfoDto.getUsername());
+        logininfor.setIpaddr(userVisitInfoDto.getIp());
+        logininfor.setLoginLocation(userVisitInfoDto.getLoginLocation());
+        logininfor.setBrowser(userVisitInfoDto.getBrowser());
+        logininfor.setOs(userVisitInfoDto.getOs());
+        logininfor.setMsg(message);
+        logininfor.setLoginTime(new Date());
+        logininfor.setCreateBy(userVisitInfoDto.getUsername());
+        // 日志状态
+        if (RyStringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
+        {
+            logininfor.setStatus(Constants.SUCCESS);
+        }
+        else if (Constants.LOGIN_FAIL.equals(status))
+        {
+            logininfor.setStatus(Constants.FAIL);
+        }
+        // 插入数据
+        SpringUtils.getBean(SysLogininforService.class).insertLogininfor(logininfor);
+    }
+
     public static String getBlock(Object msg)
     {
         if (msg == null)

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/filter/InterfaceLimitFilter.java

@@ -48,7 +48,7 @@ public class InterfaceLimitFilter extends OncePerRequestFilter {
             response.getWriter().write("访问频率过快,IP已进黑名单,请联系管理员!");
             return;
         }
-        if (!InterfaceLimitUtil.checkInterface(request, 1000, 10)) {
+        if (!InterfaceLimitUtil.checkInterface(request, 1000, 20)) {
             log.info("接口拦截:{} 请求超过限制频率【{}次/{}ms】,IP为{}", request.getRequestURI(), 10,1000, remoteIp);
             // 锁定ip黑名单
             SysBlacklist sysBlacklist = new SysBlacklist();

+ 19 - 3
backend/src/main/java/com/jiayue/ssi/filter/JwtAuthenticationTokenFilter.java

@@ -11,12 +11,13 @@ import javax.servlet.http.HttpServletResponse;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.constant.Constants;
 import com.jiayue.ssi.constant.LoginConstants;
+import com.jiayue.ssi.dto.UserVisitInfoDto;
 import com.jiayue.ssi.entity.SysUser;
 import com.jiayue.ssi.factory.LoginFactory;
 import com.jiayue.ssi.service.SysUserService;
 import com.jiayue.ssi.service.impl.UserServiceImpl;
-import com.jiayue.ssi.util.DateUtils;
-import com.jiayue.ssi.util.ResponseInfo;
+import com.jiayue.ssi.util.*;
+import eu.bitwalker.useragentutils.UserAgent;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.ExpiredJwtException;
 import lombok.RequiredArgsConstructor;
@@ -28,7 +29,6 @@ import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
-import com.jiayue.ssi.util.JwtTokenUtil;
 
 /**
  * jwt过滤器
@@ -115,6 +115,22 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
                         return;
                     }
                     if (jwtTokenUtil.validateToken(token, userDetails)) {
+                        final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
+                        String ip = IPUtils.getIpAddr(request);
+                        // token加入缓存,用于并发会话处理
+                        UserVisitInfoDto userVisitInfoDto = new UserVisitInfoDto();
+                        userVisitInfoDto.setUsername(username);
+                        userVisitInfoDto.setVtime(System.currentTimeMillis());
+                        userVisitInfoDto.setIp(ip);
+                        userVisitInfoDto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+                        // 获取客户端操作系统
+                        String os = userAgent.getOperatingSystem().getName();
+                        // 获取客户端浏览器
+                        String browser = userAgent.getBrowser().getName();
+                        userVisitInfoDto.setBrowser(browser);
+                        userVisitInfoDto.setOs(os);
+                        LoginConstants.sessionMap.put(username,userVisitInfoDto);
+
                         // 将用户信息存入 authentication,方便后续校验
                         UsernamePasswordAuthenticationToken
                                 authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

+ 17 - 3
backend/src/main/java/com/jiayue/ssi/handler/CustomAuthenticationSuccessHandler.java

@@ -14,6 +14,7 @@ import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.constant.Constants;
 import com.jiayue.ssi.constant.LoginConstants;
 import com.jiayue.ssi.constant.SecretKeyConstants;
+import com.jiayue.ssi.dto.UserVisitInfoDto;
 import com.jiayue.ssi.entity.SysAlarm;
 import com.jiayue.ssi.entity.SysPolicy;
 import com.jiayue.ssi.factory.LoginFactory;
@@ -21,6 +22,7 @@ import com.jiayue.ssi.service.SysAlarmService;
 import com.jiayue.ssi.service.SysPolicyService;
 import com.jiayue.ssi.service.SysUserService;
 import com.jiayue.ssi.util.*;
+import eu.bitwalker.useragentutils.UserAgent;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -51,6 +53,7 @@ public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthent
 
     @Override
     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+        final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
         // 验证码验证
         String username = request.getParameter("username");
         // 删除缓存邮箱口令
@@ -60,7 +63,8 @@ public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthent
         sysUser.setLockTime(0L);
         sysUser.setStatus("0");
         // 加入登录IP和时间
-        sysUser.setLoginIp(IPUtils.getIpAddr(request));
+        String ip = IPUtils.getIpAddr(request);
+        sysUser.setLoginIp(ip);
         sysUser.setLoginDate(new Date());
         sysUserService.updateUser(sysUser);
         SecurityContextHolder.getContext().setAuthentication(authentication);
@@ -71,8 +75,18 @@ public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthent
 
         String token = jwtTokenUtil.generateToken(sysUser);
         // token加入缓存,用于并发会话处理
-        LoginConstants.sessionMap.put(username,token);
-//        LoginConstants.tokenList.add(username);
+        UserVisitInfoDto userVisitInfoDto = new UserVisitInfoDto();
+        userVisitInfoDto.setUsername(username);
+        userVisitInfoDto.setVtime(System.currentTimeMillis());
+        userVisitInfoDto.setIp(ip);
+        userVisitInfoDto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+        // 获取客户端操作系统
+        String os = userAgent.getOperatingSystem().getName();
+        // 获取客户端浏览器
+        String browser = userAgent.getBrowser().getName();
+        userVisitInfoDto.setBrowser(browser);
+        userVisitInfoDto.setOs(os);
+        LoginConstants.sessionMap.put(username,userVisitInfoDto);
 
         // 将token存储内存中,便于重复登录比对
         if (CacheConstants.LOGIN_TOKEN_MAP.get(username)!=null){

+ 0 - 1
backend/src/main/java/com/jiayue/ssi/handler/CustomLogoutSuccessHandler.java

@@ -38,7 +38,6 @@ public class CustomLogoutSuccessHandler implements org.springframework.security.
         // 将token存储内存中,便于重复登录比对
         CacheConstants.LOGIN_TOKEN_MAP.remove(sysUser.getUsername());
         LoginConstants.sessionMap.remove(sysUser.getUsername());
-
         String obj = JSONUtil.toJsonStr(ResponseVO.success("退出成功"));
         // token加密处理
         String encrypt = SM2CryptUtils.encrypt(obj, SecretKeyConstants.CLIENT_PUBLIC_KEY);

+ 60 - 0
backend/src/main/java/com/jiayue/ssi/job/AutoCheckSessionMap.java

@@ -0,0 +1,60 @@
+package com.jiayue.ssi.job;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.jiayue.ssi.constant.Constants;
+import com.jiayue.ssi.constant.LoginConstants;
+import com.jiayue.ssi.dto.UserVisitInfoDto;
+import com.jiayue.ssi.entity.SysPolicy;
+import com.jiayue.ssi.factory.LoginFactory;
+import com.jiayue.ssi.service.SysLogininforService;
+import com.jiayue.ssi.service.SysOperLogService;
+import com.jiayue.ssi.service.SysPolicyService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+* 自动检测并发会话数是否存在超时
+*
+* @author xsl
+* @since 2023/06/28
+*/
+@Service
+@EnableScheduling
+@Slf4j
+public class AutoCheckSessionMap {
+    @Autowired
+    SysPolicyService sysPolicyService;
+
+    /**
+     * 每分钟执行
+     */
+    @Scheduled(cron = "0 0/1 * * * ?")
+    public void auditBak() throws Exception{
+        SysPolicy sysPolicy = sysPolicyService.getOne(new QueryWrapper<>());
+        // 非活动登出时间(分钟)
+        int inactiveLogout = sysPolicy.getInactiveLogout();
+
+        long iil = inactiveLogout * 1000 * 60;
+
+        Iterator<Map.Entry<String, UserVisitInfoDto>> entries = LoginConstants.sessionMap.entrySet().iterator();
+        while (entries.hasNext()) {
+            Map.Entry<String, UserVisitInfoDto> entry = entries.next();
+            // 判断会话连接里失效的用户
+            UserVisitInfoDto userVisitInfoDto = entry.getValue();
+            Long userTime = userVisitInfoDto.getVtime();
+            if (System.currentTimeMillis()> userTime + iil){
+                // 踢用户
+                LoginConstants.sessionMap.remove(entry.getKey());
+                // 记录用户退出日志
+                LoginFactory.autoRecordLogininfor(userVisitInfoDto, Constants.LOGIN_SUCCESS, "超时登出");
+            }
+        }
+    }
+}

+ 28 - 28
ui/src/App.vue

@@ -25,35 +25,35 @@ export default {
     //   }
     //
     // })
-    window.addEventListener('beforeunload', e => this.beforeunloadHandler(e))
-    window.addEventListener('unload', e => this.unloadHandler(e))
+    // window.addEventListener('beforeunload', e => this.beforeunloadHandler(e))
+    // window.addEventListener('unload', e => this.unloadHandler(e))
   },
   // 解除窗口关闭[监听]事件
-  destroyed() {
-    window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e))
-    window.removeEventListener('unload', e => this.unloadHandler(e))
-  },
-  methods: {
-    beforeunloadHandler(){
-      this._beforeUnload_time=new Date().getTime();
-    },
-    unloadHandler(e){
-      this._gap_time=new Date().getTime()-this._beforeUnload_time;
-      //判断是窗口关闭还是刷新
-      if(this._gap_time<=5){
-        //如果是登录状态,关闭窗口前,移除用户
-        if(sessionStorage.getItem('jy')!=null){
-          this.$axios.post(
-            '/logout', {}
-          ).then((res) => {
-            this.$message.success(res.data)
-            removeToken()
-            //注销返回自己的登录页
-            this.$router.push(`/login?redirect=${this.$route.fullPath}`)
-          })
-        }
-      }
-    }
-  }
+  // destroyed() {
+  //   window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e))
+  //   window.removeEventListener('unload', e => this.unloadHandler(e))
+  // },
+  // methods: {
+  //   beforeunloadHandler(){
+  //     this._beforeUnload_time=new Date().getTime();
+  //   },
+  //   unloadHandler(e){
+  //     this._gap_time=new Date().getTime()-this._beforeUnload_time;
+  //     //判断是窗口关闭还是刷新
+  //     if(this._gap_time<=5){
+  //       //如果是登录状态,关闭窗口前,移除用户
+  //       if(sessionStorage.getItem('jy')!=null){
+  //         this.$axios.post(
+  //           '/logout', {}
+  //         ).then((res) => {
+  //           this.$message.success(res.data)
+  //           removeToken()
+  //           //注销返回自己的登录页
+  //           this.$router.push(`/login?redirect=${this.$route.fullPath}`)
+  //         })
+  //       }
+  //     }
+  //   }
+  // }
 }
 </script>