xiangpei
2024-07-11 84333544560aec1f3be03e9870631936d039a7a1
增加session过滤器,阻止同一个用户同时登录
5个文件已修改
1个文件已添加
109 ■■■■■ 已修改文件
src/main/java/com/ycl/jxkg/config/spring/security/RestAuthenticationSuccessHandler.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/config/spring/security/RestLoginAuthenticationFilter.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/config/spring/security/RestLogoutSuccessHandler.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/config/spring/security/SecurityConfigurer.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/config/spring/security/SessionFilter.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/utils/CaffeineUtil.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/config/spring/security/RestAuthenticationSuccessHandler.java
@@ -1,10 +1,12 @@
package com.ycl.jxkg.config.spring.security;
import com.ycl.jxkg.base.SystemCode;
import com.ycl.jxkg.constants.CaffeineConstant;
import com.ycl.jxkg.domain.entity.UserEventLog;
import com.ycl.jxkg.enums.general.YesOrNoEnum;
import com.ycl.jxkg.event.UserEvent;
import com.ycl.jxkg.service.UserService;
import com.ycl.jxkg.utils.CaffeineUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
@@ -12,6 +14,7 @@
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -31,6 +34,7 @@
    private final ApplicationEventPublisher eventPublisher;
    private final UserService userService;
    private final CaffeineUtil caffeineUtil;
    /**
     * Instantiates a new Rest authentication success handler.
@@ -39,9 +43,10 @@
     * @param userService    the user service
     */
    @Autowired
    public RestAuthenticationSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService) {
    public RestAuthenticationSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService, CaffeineUtil caffeineUtil) {
        this.eventPublisher = eventPublisher;
        this.userService = userService;
        this.caffeineUtil = caffeineUtil;
    }
    @Override
@@ -49,6 +54,9 @@
        Object object = authentication.getPrincipal();
        if (null != object) {
            User springUser = (User) object;
            // 登录之后保存或更新 用户名与session的关系
            String sessionId = request.getSession().getId();
            caffeineUtil.put(CaffeineConstant.AUTH, springUser.getUsername(), sessionId);
            com.ycl.jxkg.domain.entity.User user = userService.getUserByUserName(springUser.getUsername());
            if (null != user) {
                // 密码过期返回强制修改密码标识
src/main/java/com/ycl/jxkg/config/spring/security/RestLoginAuthenticationFilter.java
@@ -2,8 +2,10 @@
import com.ycl.jxkg.config.property.CookieConfig;
import com.ycl.jxkg.utils.CaffeineUtil;
import com.ycl.jxkg.utils.JsonUtil;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -16,6 +18,7 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
/**
@@ -25,7 +28,11 @@
 * @date 2021/12/25 9:45
 */
public class RestLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private final org.slf4j.Logger logger = LoggerFactory.getLogger(RestLoginAuthenticationFilter.class);
    @Autowired
    private CaffeineUtil caffeineUtil;
    /**
     * Instantiates a new Rest login authentication filter.
@@ -40,6 +47,7 @@
        try (InputStream is = request.getInputStream()) {
            AuthenticationBean authenticationBean = JsonUtil.toJsonObject(is, AuthenticationBean.class);
            request.setAttribute(TokenBasedRememberMeServices.DEFAULT_PARAMETER, authenticationBean.isRemember());
            authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.getUserName(), authenticationBean.getPassword());
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
src/main/java/com/ycl/jxkg/config/spring/security/RestLogoutSuccessHandler.java
@@ -1,13 +1,16 @@
package com.ycl.jxkg.config.spring.security;
import com.ycl.jxkg.base.SystemCode;
import com.ycl.jxkg.constants.CaffeineConstant;
import com.ycl.jxkg.domain.entity.User;
import com.ycl.jxkg.domain.entity.UserEventLog;
import com.ycl.jxkg.event.UserEvent;
import com.ycl.jxkg.service.UserService;
import com.ycl.jxkg.utils.CaffeineUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.stereotype.Component;
@@ -27,6 +30,7 @@
    private final ApplicationEventPublisher eventPublisher;
    private final UserService userService;
    private final CaffeineUtil caffeineUtil;
    /**
     * Instantiates a new Rest logout success handler.
@@ -35,15 +39,19 @@
     * @param userService    the user service
     */
    @Autowired
    public RestLogoutSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService) {
    public RestLogoutSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService, CaffeineUtil caffeineUtil) {
        this.eventPublisher = eventPublisher;
        this.userService = userService;
        this.caffeineUtil = caffeineUtil;
    }
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        org.springframework.security.core.userdetails.User springUser = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
        if (null != springUser) {
            // 清除用户名和sessionId之间的绑定
            caffeineUtil.remove(CaffeineConstant.AUTH, springUser.getUsername());
            SecurityContextHolder.clearContext();
            User user = userService.getUserByUserName(springUser.getUsername());
            UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
            userEventLog.setContent(user.getUserName() + " 登出了学之思开源考试系统");
src/main/java/com/ycl/jxkg/config/spring/security/SecurityConfigurer.java
@@ -67,6 +67,12 @@
            this.restAccessDeniedHandler = restAccessDeniedHandler;
        }
        @Bean
        public SessionFilter sessionFilter() throws Exception {
            SessionFilter jwtTokenFilter = new SessionFilter(authenticationManagerBean());
            return jwtTokenFilter;
        }
        /**
         * @param http http
         * @throws Exception exception
@@ -95,6 +101,7 @@
                    .and().rememberMe().key(CookieConfig.getName()).tokenValiditySeconds(CookieConfig.getInterval()).userDetailsService(formDetailsService)
                    .and().csrf().disable()
                    .cors();
            http.addFilter(sessionFilter());
        }
src/main/java/com/ycl/jxkg/config/spring/security/SessionFilter.java
New file
@@ -0,0 +1,61 @@
package com.ycl.jxkg.config.spring.security;
import com.ycl.jxkg.base.SystemCode;
import com.ycl.jxkg.constants.CaffeineConstant;
import com.ycl.jxkg.utils.CaffeineUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Objects;
/**
 * @author 29443
 * @date 2022/4/4
 */
@Slf4j
public class SessionFilter extends BasicAuthenticationFilter {
    @Autowired
    private CaffeineUtil caffeineUtil;
    public SessionFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 检查请求中是否有security设置的认证信息。没有的话本Filter不做处理
        SecurityContextImpl securityContext = (SecurityContextImpl) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
        if (Objects.isNull(securityContext)) {
            chain.doFilter(request, response);
            return;
        }
        // 有的话验证
        Authentication authentication = securityContext.getAuthentication();
        User authUser = (User) authentication.getPrincipal();
        // 检查这个用户对应的sessionId是否和登录时的sessionId相等
        String loginSessionId = (String) caffeineUtil.get(CaffeineConstant.AUTH, authUser.getUsername());
        if (! request.getSession().getId().equals(loginSessionId)) {
            log.warn("检测到同一账号两个浏览器登录");
            RestUtil.response(response, SystemCode.UNAUTHORIZED.getCode(), "当前登录已失效");
        } else {
            chain.doFilter(request, response);
        }
    }
}
src/main/java/com/ycl/jxkg/utils/CaffeineUtil.java
@@ -56,6 +56,19 @@
    }
    /**
     * 删除
     *
     * @param database 数据库/ caffeine map的key
     * @param key map的key
     */
    public void remove(String database, String key) {
        Map<String, Object> map = (Map<String, Object>) caffeineCache.getIfPresent(database);
        if (Objects.nonNull(map)) {
            map.remove(key);
        }
    }
    /**
     * 创建key
     *
     * @param database