zoukankan      html  css  js  c++  java
  • SpringSecurity(十): 只允许一个用户登录

    只允许一个用户在一个地方登录,也是每个用户在系统中只能有一个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

  • 相关阅读:
    浏览器同源政策及其规避方法---转阮大神
    js跨域详解
    js中top、self、parent
    杂记
    DOM 踩踩踩
    java idea 连接数据库
    数据库mySQL常用命令
    用迭代实现80人围成一圈逢3取出
    如何把通过类建立的对象存入数组中.
    面向对象编程
  • 原文地址:https://www.cnblogs.com/yscec/p/14319868.html
Copyright © 2011-2022 走看看