Selaa lähdekoodia

增加并发会话数限制级连接超时后记录审计

xusl 1 vuosi sitten
vanhempi
commit
3eee050aaf

+ 6 - 2
backend/src/main/java/com/jiayue/ssi/config/WebSecurityConfig.java

@@ -6,6 +6,7 @@ import com.jiayue.ssi.service.SysUserService;
 import com.jiayue.ssi.service.impl.UserServiceImpl;
 import com.jiayue.ssi.util.JwtTokenUtil;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
@@ -47,7 +48,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     JwtTokenUtil jwtTokenUtil;
     @Autowired
     SysUserService sysUserService;
-//    @Autowired
+    @Value("${bfhhs}")
+    private int bfhhs;
+
+    //    @Autowired
 //    XssEscapeFilter xssEscapeFilter;
 //    @Autowired
 //    XssKeywordsFilter xssKeywordsFilter;
@@ -91,7 +95,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         httpSecurity.addFilterBefore(new SqlFilter(), LogoutFilter.class);
         httpSecurity.addFilterBefore(new VerifyCodeFilter(), LogoutFilter.class);
         httpSecurity.addFilterBefore(new MailCodeFilter(), LogoutFilter.class);
-        httpSecurity.addFilterBefore(new JwtAuthenticationTokenFilter(userServiceImpl, jwtTokenUtil, sysUserService), LogoutFilter.class);
+        httpSecurity.addFilterBefore(new JwtAuthenticationTokenFilter(userServiceImpl, jwtTokenUtil, sysUserService,bfhhs), LogoutFilter.class);
         httpSecurity.headers().frameOptions().disable();
         httpSecurity.headers().httpStrictTransportSecurity().includeSubDomains(true).preload(true).maxAgeInSeconds(31536000);
         httpSecurity

+ 12 - 0
backend/src/main/java/com/jiayue/ssi/constant/LoginConstants.java

@@ -1,4 +1,11 @@
 package com.jiayue.ssi.constant;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
 /**
 * 登录用到的常量
 *
@@ -10,4 +17,9 @@ public class LoginConstants {
      * 登录锁定后多久时间重试(毫秒)
      */
     public static final Long LOGIN_LOCK = 100000L;
+
+    /**
+     * 并发会话存储
+     */
+    public static ConcurrentMap<String, String> sessionMap = new ConcurrentHashMap<>();
 }

+ 80 - 40
backend/src/main/java/com/jiayue/ssi/filter/JwtAuthenticationTokenFilter.java

@@ -9,11 +9,16 @@ import javax.servlet.http.HttpServletRequest;
 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.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 io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.core.annotation.Order;
@@ -40,11 +45,13 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
     UserServiceImpl userServiceImpl;
     JwtTokenUtil jwtTokenUtil;
     SysUserService sysUserService;
+    int bfhhs;
 
-    public JwtAuthenticationTokenFilter(UserServiceImpl userServiceImpl, JwtTokenUtil jwtTokenUtil,SysUserService sysUserService) {
+    public JwtAuthenticationTokenFilter(UserServiceImpl userServiceImpl, JwtTokenUtil jwtTokenUtil,SysUserService sysUserService,int bfhhs) {
         this.userServiceImpl = userServiceImpl;
         this.jwtTokenUtil = jwtTokenUtil;
         this.sysUserService = sysUserService;
+        this.bfhhs = bfhhs;
     }
 
     @Override
@@ -53,63 +60,96 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
         SecurityContextHolder.getContext().getAuthentication();
         String token = request.getHeader("Authorization");
         if (!StringUtils.isEmpty(token)) {
-            String username = jwtTokenUtil.getUsernameFromToken(token);
-            if (username != null) {
-                if (CacheConstants.LOGIN_TOKEN_MAP.get(username) == null) {
+            try {
+                Claims claims = jwtTokenUtil.getClaimsFromToken(token);
+                if (claims==null){
+                    // 无效token
                     ResponseInfo.doResponse(response, "令牌无效,请重新登录!", 403);
                     return;
-                } else {
-                    String cacheToken = CacheConstants.LOGIN_TOKEN_MAP.get(username);
-                    //内存token和当前token一致  说明是当前登陆用户访问
-                    if (!token.equals(cacheToken)) {
-                        ResponseInfo.doResponse(response, "账号已在另一台机器登录,请重新登录!", 403);
+                }
+
+                String username = claims.getSubject();
+                if (username != null) {
+                    if (CacheConstants.LOGIN_TOKEN_MAP.get(username) == null) {
+                        ResponseInfo.doResponse(response, "令牌无效,请重新登录!", 403);
                         return;
+                    } else {
+                        String cacheToken = CacheConstants.LOGIN_TOKEN_MAP.get(username);
+                        //内存token和当前token一致  说明是当前登陆用户访问
+                        if (!token.equals(cacheToken)) {
+                            ResponseInfo.doResponse(response, "账号已在另一台机器登录,请重新登录!", 403);
+                            return;
+                        }
                     }
                 }
-            }
 
-            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
-                UserDetails userDetails = userServiceImpl.loadUserByUsername(username);
-                SysUser user = (SysUser) userDetails;
-                if (user.getStatus().equals("2")){
-                    ResponseInfo.doResponse(response, "账号已注销,不能登录!", 403);
-                    return;
-                }
-                if (user.getExpDate() != null) {
-                    // 判断账号截止日期
-                    Date lastDate = DateUtils.getDayLastTime(user.getExpDate());
-                    if (new Date().after(lastDate)) {
-                        if ("0".equals(user.getStatus())){
-                            // 将正常状态变为锁定
+                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+                    UserDetails userDetails = userServiceImpl.loadUserByUsername(username);
+                    SysUser user = (SysUser) userDetails;
+                    if (user.getStatus().equals("2")){
+                        ResponseInfo.doResponse(response, "账号已注销,不能登录!", 403);
+                        return;
+                    }
+                    if (user.getExpDate() != null) {
+                        // 判断账号截止日期
+                        Date lastDate = DateUtils.getDayLastTime(user.getExpDate());
+                        if (new Date().after(lastDate)) {
+                            if ("0".equals(user.getStatus())){
+                                // 将正常状态变为锁定
 //                            user.setLockTime(System.currentTimeMillis());
-                            user.setStatus("1");
-                            Boolean bo = sysUserService.updateUser(user);
-                            if (!bo){
-                                log.info(user.getUsername()+"账号已过有效期被锁定失败");
-                            }
-                            else{
-                                log.info(user.getUsername()+"账号已过有效期被锁定成功");
+                                user.setStatus("1");
+                                Boolean bo = sysUserService.updateUser(user);
+                                if (!bo){
+                                    log.info(user.getUsername()+"账号已过有效期被锁定失败");
+                                }
+                                else{
+                                    log.info(user.getUsername()+"账号已过有效期被锁定成功");
+                                }
                             }
+                            ResponseInfo.doResponse(response, "账号已过有效期被锁定,请联系管理员!", 403);
+                            return;
                         }
-                        ResponseInfo.doResponse(response, "账号已过有效期被锁定,请联系管理员!", 403);
+                    }
+                    if ("1".equals(user.getStatus()) && user.getLockTime()==0){
+                        ResponseInfo.doResponse(response, "账号已被锁定,请联系管理员!", 403);
                         return;
                     }
+                    if (jwtTokenUtil.validateToken(token, userDetails)) {
+                        // 将用户信息存入 authentication,方便后续校验
+                        UsernamePasswordAuthenticationToken
+                                authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                        // 将 authentication 存入 ThreadLocal,方便后续获取用户信息
+                        SecurityContextHolder.getContext().setAuthentication(authentication);
+                    }
                 }
-                if ("1".equals(user.getStatus()) && user.getLockTime()==0){
-                    ResponseInfo.doResponse(response, "账号已被锁定,请联系管理员!", 403);
+            }
+            catch (ExpiredJwtException expiredJwtException){
+                Claims claims = expiredJwtException.getClaims();
+                String username = claims.getSubject();
+                String cacheToken = CacheConstants.LOGIN_TOKEN_MAP.get(username);
+                //内存token和当前token一致  说明是当前登陆用户访问
+                if (cacheToken!=null && !token.equals(cacheToken)) {
+                    ResponseInfo.doResponse(response, "账号已在另一台机器登录,请重新登录!", 403);
                     return;
                 }
-                if (jwtTokenUtil.validateToken(token, userDetails)) {
-                    // 将用户信息存入 authentication,方便后续校验
-                    UsernamePasswordAuthenticationToken
-                            authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
-                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
-                    // 将 authentication 存入 ThreadLocal,方便后续获取用户信息
-                    SecurityContextHolder.getContext().setAuthentication(authentication);
+                else{
+                    // 登录连接超时,保存审计
+                    LoginFactory.recordLogininfor(claims.getSubject(), Constants.LOGIN_FAIL, "连接超时");
+                    // 将token存储内存中,便于重复登录比对
+                    CacheConstants.LOGIN_TOKEN_MAP.remove(claims.getSubject());
+                    LoginConstants.sessionMap.remove(claims.getSubject());
+                    ResponseInfo.doResponse(response, "令牌无效,请重新登录!", 403);
+                    return;
                 }
             }
         } else {
             if ("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())) {
+                // 判断并发会话数是否满足
+                if (LoginConstants.sessionMap.size()+1>bfhhs){
+                    ResponseInfo.doResponse(response, "系统会话数已满,不能登录!", 401);
+                    return;
+                }
                 // 用户名密码登录提交,判断账号有效期
                 try {
                     UserDetails userDetails = userServiceImpl.loadUserByUsername(request.getParameter("username"));

+ 5 - 5
backend/src/main/java/com/jiayue/ssi/filter/VerifySmFilter.java

@@ -57,11 +57,11 @@ public class VerifySmFilter extends OncePerRequestFilter {
                         ResponseInfo.doResponse(response, "token验签失败,不能访问系统!", 401);
                         return;
                     }
-                    JwtTokenUtil jwtTokenUtil = new JwtTokenUtil();
-                    if (jwtTokenUtil.isTokenExpired(decryptTokenStr)) {
-                        ResponseInfo.doResponse(response, "token令牌无效,请重新登录!", 403);
-                        return;
-                    }
+//                    JwtTokenUtil jwtTokenUtil = new JwtTokenUtil();
+//                    if (jwtTokenUtil.isTokenExpired(decryptTokenStr)) {
+//                        ResponseInfo.doResponse(response, "token令牌无效,请重新登录!", 403);
+//                        return;
+//                    }
 
                     initWrapper.addHeader("Authorization", decryptTokenStr);
                 } else {

+ 6 - 1
backend/src/main/java/com/jiayue/ssi/handler/CustomAuthenticationSuccessHandler.java

@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.jiayue.ssi.config.SendMailUtil;
 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.entity.SysAlarm;
 import com.jiayue.ssi.entity.SysPolicy;
@@ -65,10 +66,14 @@ public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthent
         SecurityContextHolder.getContext().setAuthentication(authentication);
         CacheConstants.usernamePasswordMap.put(username,request.getParameter("password"));
 
-        // 记录用户退出日志
+        // 记录用户登录日志
         LoginFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
 
         String token = jwtTokenUtil.generateToken(sysUser);
+        // token加入缓存,用于并发会话处理
+        LoginConstants.sessionMap.put(username,token);
+//        LoginConstants.tokenList.add(username);
+
         // 将token存储内存中,便于重复登录比对
         if (CacheConstants.LOGIN_TOKEN_MAP.get(username)!=null){
             // 之前有用户登录过,本次将上次用户踢出下线。并通知管理员

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

@@ -3,6 +3,7 @@ package com.jiayue.ssi.handler;
 import cn.hutool.json.JSONUtil;
 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.entity.SysUser;
 import com.jiayue.ssi.factory.LoginFactory;
@@ -36,6 +37,7 @@ public class CustomLogoutSuccessHandler implements org.springframework.security.
         LoginFactory.recordLogininfor(sysUser.getUsername(), Constants.LOGIN_SUCCESS, "退出成功");
         // 将token存储内存中,便于重复登录比对
         CacheConstants.LOGIN_TOKEN_MAP.remove(sysUser.getUsername());
+        LoginConstants.sessionMap.remove(sysUser.getUsername());
 
         String obj = JSONUtil.toJsonStr(ResponseVO.success("退出成功"));
         // token加密处理

+ 31 - 15
backend/src/main/java/com/jiayue/ssi/util/JwtTokenUtil.java

@@ -56,8 +56,13 @@ public class JwtTokenUtil {
         Claims claims;
         try {
             claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
-        } catch (Exception e) {
-//            e.printStackTrace();
+        }
+        catch (ExpiredJwtException e) {
+            // token过期
+            throw e;
+        }
+        catch (Exception e) {
+            // 无效token
             claims = null;
         }
         return claims;
@@ -156,19 +161,30 @@ public class JwtTokenUtil {
 
     public static void main(String[] args)throws Exception {
         JwtTokenUtil jwtTokenUtil = new JwtTokenUtil();
-        Map<String, Object> claims = new HashMap<String, Object>(16);
-        claims.put(Claims.SUBJECT, "Test");
-//        claims.put(Claims.ISSUED_AT, new Date(System.currentTimeMillis()));
-        String token=jwtTokenUtil.generateToken(claims);
-        System.out.println(jwtTokenUtil.getUsernameFromToken(token));
-        System.out.println(jwtTokenUtil.isTokenExpired(token));
-        Claims claims1 = jwtTokenUtil.getClaimsFromToken(token);
-//        System.out.println("过期时间:"+claims1.getExpiration());
-//        System.out.println("过期时间提前2分钟:"+new Date(claims1.getExpiration().getTime()-1000*60*2));
-//        Thread.sleep(3000L);
-        System.out.println(token);
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-        System.out.println("签发时间:"+ sdf.format(claims1.getIssuedAt()));
+//        Map<String, Object> claims = new HashMap<String, Object>(16);
+//        claims.put(Claims.SUBJECT, "Test");
+////        claims.put(Claims.ISSUED_AT, new Date(System.currentTimeMillis()));
+//        String token=jwtTokenUtil.generateToken(claims);
+//        System.out.println(jwtTokenUtil.getUsernameFromToken(token));
+//        System.out.println(jwtTokenUtil.isTokenExpired(token));
+//        Claims claims1 = jwtTokenUtil.getClaimsFromToken(token);
+////        System.out.println("过期时间:"+claims1.getExpiration());
+////        System.out.println("过期时间提前2分钟:"+new Date(claims1.getExpiration().getTime()-1000*60*2));
+////        Thread.sleep(3000L);
+//        System.out.println(token);
+//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//        System.out.println("签发时间:"+ sdf.format(claims1.getIssuedAt()));
+
+        try {
+            Claims claims = jwtTokenUtil.getClaimsFromToken("eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJUZXN0IiwiZXhwIjoxNjc5MzA0MTUyLCJpYXQiOjE2NzkzMDIzNTJ9.Mtv8qJHbjLdnf52VRodqYqp1B2RI1iB_SxzIO92QccwmshQasJOqQL_rhRhgHFcwZRkUmfdwPy06UsWEr3aa7w");
+            if (claims==null){
+                System.out.println("无效");
+            }
+        }
+        catch (ExpiredJwtException expiredJwtException){
+            System.out.println("过期:"+expiredJwtException.getClaims().getSubject());
+        }
+//        System.out.println(jwtTokenUtil.getUsernameFromToken("eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJUZXN0IiwiZXhwIjoxNjc5MzA0MTUyLCJpYXQiOjE2NzkzMDIzNTJ9.Mtv8qJHbjLdnf52VRodqYqp1B2RI1iB_SxzIO92QccwmshQasJOqQL_rhRhgHFcwZRkUmfdwPy06UsWEr3zZab"));
 // eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJUZXN0IiwiZXhwIjoxNjc5MzA0MTUyLCJpYXQiOjE2NzkzMDIzNTJ9.Mtv8qJHbjLdnf52VRodqYqp1B2RI1iB_SxzIO92QccwmshQasJOqQL_rhRhgHFcwZRkUmfdwPy06UsWEr3zZ7w
 // eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJUZXN0IiwiZXhwIjoxNjc5MzA0MTY5LCJpYXQiOjE2NzkzMDIzNjl9.x0CiNFezdegvRzmFSQyCzzmNrA-N5g4QaHMZOQubThx6LIOFM8jkoPJgEWc8xUlUipTW5CqFKfxn0z0O0xVvoQ
     }

+ 3 - 31
backend/src/main/resources/application.yml

@@ -14,6 +14,8 @@ server:
 logging:
   config: classpath:logback-ssi.xml
 
+bfhhs: 10
+
 #设置提供的服务名
 spring:
   application:
@@ -24,7 +26,7 @@ spring:
       stat-view-servlet.enabled: false
     driver-class-name: com.mysql.cj.jdbc.Driver
     type: com.alibaba.druid.pool.DruidDataSource
-    url: ENC(l2aVLkmElPR0jDOM97B5uHXw0anpdP+WLCrEYXDe/l8Wzd8Gva8f7OslEs5Pxevq7bgLnF0T/tsoof3mFP4qednwYDIF2zWQ7fswoeFTUZttJ/gnhJbKIWwSEkQ+dXPdvYItXgVl2daQz/ujzOt9uuVvOh8l5P/HgSFBtu8aTdF9vDctzPDqUpcLS0meRHV/2g+wsiTRIvR4dtWPGfbGz7iULjqcYJTwqzZzfofGQlUHcnU1uatiJQ==)
+    url: jdbc:mysql://localhost:3307/ssi?useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&autoReconnect=true&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
     username: ENC(UN5TVXHQJt2DbXQiX/GRsw==)
     password: ENC(r9wV3VlMJEcxqJ6hYeG7BLRdUMXsPQHQ)
 
@@ -56,36 +58,6 @@ spring:
           #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误
         debug: false
 
-#mybatis plus
-#mybatis-plus:
-#  #指明mapper.xml扫描位置(classpath* 代表编译后类文件根目录)
-#  mapper-locations: classpath*:/mapper/**Mapper.xml
-#  #指明实体扫描(多个package用逗号或者分号分隔)
-#  typeAliasesPackage: com.ssqmybatis.entity;
-#  global-config:
-#    #主键类型 0:数据库ID自增, 1:用户输入ID,2:全局唯一ID (数字类型唯一ID), 3:全局唯一ID UUID
-#    id-type: 0
-#    #字段策略(拼接sql时用于判断属性值是否拼接) 0:忽略判断,1:非NULL判断,2:非空判断
-#    field-strategy: 2
-#    #驼峰下划线转换含查询column及返回column(column下划线命名create_time,返回java实体是驼峰命名createTime,开启后自动转换否则保留原样)
-#    db-column-underline: true
-#    #是否动态刷新mapper
-#    refresh-mapper: false
-#    #数据库大写命名下划线转换
-#    #capital-mode: true
-
-#mybatis:
-#  table:
-#    auto: update
-#    #create	    系统启动后,会将所有的表删除掉,然后根据model中配置的结构重新建表,该操作会破坏原有数据。
-#    #update	    系统会自动判断哪些表是新建的,哪些字段要修改类型等,哪些字段要删除,哪些字段要新增,该操作不会破坏原有数据。
-#    #none 		系统不做任何处理。
-#    #add		新增表/新增字段/新增索引/新增唯一约束的功能,不做做修改和删除 (只在版本1.0.9.RELEASE及以上支持)。
-#  model:
-#    pack: com.ssqmybatis.entity #扫描用于创建表的对象的包名,多个包用“,”隔开
-#  database:
-#    type: mysql #数据库类型 目前只支持mysql
-#  mapper-locations: classpath:mapper/*.xml
 mybatis-plus:
   typeAliasesPackage: com.jiayue.ssi.entity
   configuration: