只允許一個用戶在一個地方登錄,也是每個用戶在系統中只能有一個Session。如果同一用戶在第2個地方登錄,則將第1個踢下線。
1.自定義 CustomSessionInformationExpiredStrategy 實現類來定制策略
/** * 同一用戶只允許一台電腦登錄 * 同一用戶在第2個地方登錄,則將第1個踢下線 * 當同一用戶的 session 達到指定數量時,執行此類 */ @Component("customSessionInformationExpiredStrategy") public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy { @Autowired CustomAuthenticationFailureHandler customAuthenticationFailureHandler; @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException { //獲取用戶名 UserDetails userDetails = (UserDetails) event.getSessionInformation().getPrincipal(); AuthenticationException exception = new AuthenticationServiceException(String.format("[%s]用戶在另外一台電腦登錄,您已被下線", userDetails.getUsername())); try { //當用戶在另外一台電腦登錄后,交給失敗處理器響應給前端json數據 customAuthenticationFailureHandler.onAuthenticationFailure(event.getRequest(), event.getResponse(), exception); } catch (ServletException e) { e.printStackTrace(); } } }
2.將自定義CustomSessionInformationExpiredStrategy實例 注入到安全配置類SpringSecurityConfig中,並進行配置
/** * 安全配置類作為安全控制中心, 用於實現身份認證與授權配置功能 */ @Configuration @EnableWebSecurity //啟動 SpringSecurity 過濾器鏈功能 public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired SecurityProperties securityProperties; Logger logger = LoggerFactory.getLogger(SpringSecurityConfig.class); @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { // 加密存儲 明文+隨機鹽值 return new BCryptPasswordEncoder(); } @Autowired CustomUserDetailsService customUserDetailsService; /** * 認證管理器: * 1、認證信息提供方式(用戶名、密碼、當前用戶的資源權限) * 2、可采用內存存儲方式,也可能采用數據庫方式等 * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //基於內存存儲認證信息 存儲的密碼必須是加密后的 不然會報錯:There is no PasswordEncoder mapped for the id "null" //auth.inMemoryAuthentication().withUser("zcc").password("123").authorities("ADMIN"); /*String password = bCryptPasswordEncoder().encode("123"); logger.info("加密后的密碼:" + password); auth.inMemoryAuthentication().withUser("zcc").password(password).authorities("ADMIN");*/ // 指定使用自定義查詢用戶信息來完成身份認證 auth.userDetailsService(customUserDetailsService); } @Autowired CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; @Autowired CustomAuthenticationFailureHandler customAuthenticationFailureHandler; @Autowired ImageVerifyCodeValidateFilter imageVerifyCodeValidateFilter; @Autowired SmsVerifyCodeValidateFilter smsVerifyCodeValidateFilter; @Autowired MobileAuthenticationConfig mobileAuthenticationConfig; @Autowired CustomInvalidSessionStrategy customInvalidSessionStrategy; @Autowired CustomSessionInformationExpiredStrategy customSessionInformationExpiredStrategy; /** * 記住我 功能 */ @Autowired DataSource dataSource; @Bean public JdbcTokenRepositoryImpl jdbcTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); // 是否啟動時自動創建表,第一次啟動創建就行,后面啟動把這個注釋掉,不然報錯已存在表 //jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } /** * 資源權限配置(過濾器鏈): * 1、被攔截的資源 * 2、資源所對應的角色權限 * 3、定義認證方式:httpBasic 、httpForm * 4、定制登錄頁面、登錄請求地址、錯誤處理方式 * 5、自定義 spring security 過濾器 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { //http.httpBasic()//采用httpBasic 認證方式 /*http.formLogin() .loginPage("/login/page")// 交給 /login/page 響應認證(登錄)頁面 .loginProcessingUrl("/login/form") // 登錄表單提交處理Url, 默認是 /login .usernameParameter("name") // 默認用戶名的屬性名是 username .passwordParameter("pwd") // 默認密碼的屬性名是 password .and() .authorizeRequests()//認證請求 .antMatchers("/login/page").permitAll()//自定義登錄頁不需要認證 .anyRequest().authenticated();// 所有進入應用的HTTP請求都要進行認證*/ http .addFilterBefore(imageVerifyCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)//將校驗過濾器 imageCodeValidateFilter 添加到 UsernamePasswordAuthenticationFilter 前面 .addFilterBefore(smsVerifyCodeValidateFilter,UsernamePasswordAuthenticationFilter.class)//將校驗過濾器 smsVerifyCodeValidateFilter 添加到 UsernamePasswordAuthenticationFilter 前面 .formLogin() .loginPage(securityProperties.getLoginPage())// 交給 /login/page 響應認證(登錄)頁面 .loginProcessingUrl(securityProperties.getLoginProcessingUrl()) // 登錄表單提交處理Url, 默認是 /login .usernameParameter(securityProperties.getUsernameParameter()) // 默認用戶名的屬性名是 username .passwordParameter(securityProperties.getPasswordParameter()) // 默認密碼的屬性名是 password .successHandler(customAuthenticationSuccessHandler)//自定義認證成功處理器 .failureHandler(customAuthenticationFailureHandler)//自定義認證失敗處理器 .and() .authorizeRequests()//認證請求 .antMatchers(securityProperties.getLoginPage(),securityProperties.getMobilePage(),securityProperties.getImageCodeUrl(),securityProperties.getMobileCodeUrl()).permitAll()//自定義登錄頁不需要認證,生成圖片驗證碼,發送短信獲取驗證碼也不需要驗證 .anyRequest().authenticated()// 所有進入應用的HTTP請求都要進行認證 .and() .rememberMe()//記住我功能 .tokenRepository(jdbcTokenRepository())//保存登錄信息 .tokenValiditySeconds(securityProperties.getTokenValiditySeconds())//記住我有效時長一周 .and() .sessionManagement()//session會話管理 .invalidSessionStrategy(customInvalidSessionStrategy)//當session失效后的處理類 .maximumSessions(1)// 每個用戶在系統中的最大session數 .expiredSessionStrategy(customSessionInformationExpiredStrategy)//當用戶達到最大session數后,則調用此處的實現 ; // 將手機相關的配置綁定過濾器鏈上 http.apply(mobileAuthenticationConfig); } /** * 放行靜態資源(js css 等) * * @param web */ @Override public void configure(WebSecurity web) { //web.ignoring().antMatchers("/dist/**", "/modules/**", "/plugins/**"); web.ignoring().antMatchers(securityProperties.getStaticPaths()); } }
3、測試
完整代碼地址:https://gitee.com/zhechaochao/security-parent.git