Преглед на файлове

spring-security 中使用自定义的 AuthenticationFilter 时, 需要自定义多端同时登录的 session 处理

reghao преди 2 години
родител
ревизия
d33fc45f9b

+ 28 - 3
manager/src/main/java/cn/reghao/devops/manager/account/security/WebSecurityConfig.java

@@ -16,13 +16,18 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.session.SessionRegistry;
 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.logout.LogoutHandler;
 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.security.web.authentication.session.*;
 import org.springframework.security.web.context.SecurityContextPersistenceFilter;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Web 应用安全配置
  *
@@ -43,16 +48,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     private final AuthenticationFailureHandler failureHandler;
     private final LogoutHandler logoutHandler;
     private final LogoutSuccessHandler logoutSuccessHandler;
+    private final SessionRegistry sessionRegistry;
 
     public WebSecurityConfig(AccountAuthProvider userAuthProvider, AccountAuthService accountAuthService,
                              AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler,
-                             LogoutHandler logoutHandler, LogoutSuccessHandler logoutSuccessHandler) {
+                             LogoutHandler logoutHandler, LogoutSuccessHandler logoutSuccessHandler,
+                             SessionRegistry sessionRegistry) {
         this.userAuthProvider = userAuthProvider;
         this.accountAuthService = accountAuthService;
         this.successHandler = successHandler;
         this.failureHandler = failureHandler;
         this.logoutHandler = logoutHandler;
         this.logoutSuccessHandler = logoutSuccessHandler;
+        this.sessionRegistry = sessionRegistry;
     }
 
     /**
@@ -82,7 +90,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
         // 配置 FilterChainProxy 过滤器链
         http.addFilterAfter(new LoginRedirectFilter(), SecurityContextPersistenceFilter.class);
-        http.addFilterBefore(accountAuthFilter(), UsernamePasswordAuthenticationFilter.class);
+        http.addFilterBefore(accountAuthFilter(sessionRegistry), UsernamePasswordAuthenticationFilter.class);
 
         // 禁用 UsernamePasswordAuthenticationFilter, 使用自定义的 AccountAuthFilter
         http.formLogin().disable();
@@ -151,14 +159,31 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
      * @return
      * @date 2022-07-06 上午9:54
      */
-    private AccountAuthFilter accountAuthFilter() throws Exception {
+    @Bean
+    public AccountAuthFilter accountAuthFilter(SessionRegistry sessionRegistry) throws Exception {
         AccountAuthFilter filter = new AccountAuthFilter(loginApi, "POST", accountAuthService);
         filter.setAuthenticationManager(super.authenticationManager());
         filter.setAuthenticationSuccessHandler(successHandler);
         filter.setAuthenticationFailureHandler(failureHandler);
+        filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy(sessionRegistry));
         return filter;
     }
 
+    public CompositeSessionAuthenticationStrategy sessionAuthenticationStrategy(SessionRegistry sessionRegistry){
+        ConcurrentSessionControlAuthenticationStrategy controlAuthenticationStrategy =
+                new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
+        // 设置帐号同时登录的最大数量
+        controlAuthenticationStrategy.setMaximumSessions(1);
+        //controlAuthenticationStrategy.setExceptionIfMaximumExceeded(true);
+
+        List<SessionAuthenticationStrategy> authenticationStrategies = new ArrayList<>();
+        authenticationStrategies.add(controlAuthenticationStrategy);
+        authenticationStrategies.add(new SessionFixationProtectionStrategy());
+        authenticationStrategies.add(new RegisterSessionAuthenticationStrategy(sessionRegistry));
+
+        return new CompositeSessionAuthenticationStrategy(authenticationStrategies);
+    }
+
     /**
      * 角色继承
      * ADMIN 可以访问 USER 的权限,反之不可

+ 34 - 0
manager/src/main/java/cn/reghao/devops/manager/account/security/session/SecuritySessionConfig.java

@@ -0,0 +1,34 @@
+package cn.reghao.devops.manager.account.security.session;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.session.SessionRegistry;
+import org.springframework.security.core.session.SessionRegistryImpl;
+import org.springframework.security.web.session.ConcurrentSessionFilter;
+import org.springframework.security.web.session.HttpSessionEventPublisher;
+
+/**
+ * spring-security 中涉及 session 处理的相关配置
+ *
+ * @author reghao
+ * @date 2024-02-02 17:18:12
+ */
+@Configuration
+public class SecuritySessionConfig {
+    @Bean
+    public SessionRegistry sessionRegistry() {
+        return new SessionRegistryImpl();
+    }
+
+    @Bean
+    public ConcurrentSessionFilter concurrentSessionFilter(SessionRegistry sessionRegistry,
+                                                           SessionExpiredStrategy sessionExpiredStrategy){
+        // 帐号同时登录数量超过限制时的处理类
+        return new ConcurrentSessionFilter(sessionRegistry, sessionExpiredStrategy);
+    }
+
+    @Bean
+    public HttpSessionEventPublisher httpSessionEventPublisher() {
+        return new HttpSessionEventPublisher();
+    }
+}

+ 33 - 0
manager/src/main/java/cn/reghao/devops/manager/account/security/session/SessionExpiredStrategy.java

@@ -0,0 +1,33 @@
+package cn.reghao.devops.manager.account.security.session;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.web.session.SessionInformationExpiredEvent;
+import org.springframework.security.web.session.SessionInformationExpiredStrategy;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * ConcurrentSessionFilter 中检测到过期 session 时的处理策略
+ *
+ * @author reghao
+ * @date 2024-02-02 16:32:43
+ */
+@Slf4j
+@Component
+public class SessionExpiredStrategy implements SessionInformationExpiredStrategy {
+    @Override
+    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
+        String principal = (String) event.getSessionInformation().getPrincipal();
+        log.info("{} 已在别处登录!", principal);
+
+        HttpServletResponse response = event.getResponse();
+        /*response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.setContentType("application/json;charset=utf-8");
+        response.getWriter().write("您的账号已经在别的地方登录!");*/
+        String redirectPath = "/login";
+        response.sendRedirect(redirectPath);
+    }
+}

+ 0 - 7
manager/src/main/java/cn/reghao/devops/manager/config/spring/AppLifecycle.java

@@ -1,9 +1,6 @@
 package cn.reghao.devops.manager.config.spring;
 
 import cn.reghao.devops.common.build.model.constant.CompileType;
-import cn.reghao.devops.manager.account.db.repository.MenuRepository;
-import cn.reghao.devops.manager.account.model.dto.AccountRole;
-import cn.reghao.devops.manager.account.model.po.Menu;
 import cn.reghao.devops.manager.account.service.AccountService;
 import cn.reghao.devops.manager.account.service.FileService;
 import cn.reghao.devops.manager.account.service.RoleService;
@@ -13,9 +10,6 @@ import cn.reghao.devops.manager.app.service.config.BuildDirService;
 import cn.reghao.devops.manager.log.Appenders;
 import cn.reghao.devops.manager.log.LoggerConfig;
 import cn.reghao.devops.manager.machine.service.MachineService;
-import cn.reghao.devops.manager.account.db.repository.RoleRepository;
-import cn.reghao.devops.manager.account.model.constant.RoleType;
-import cn.reghao.devops.manager.account.model.po.Role;
 import cn.reghao.devops.manager.ws.handler.LogHandler;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.DisposableBean;
@@ -25,7 +19,6 @@ import org.springframework.stereotype.Component;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 
 /**