SpringSecurity整合springBoot、redis实现登录互踢功能

背景

基于我的文章——《SpringSecurity整合springBoot、redis token动态url权限校验》。要实现的功能是要实现一个用户不可以同时在两台设备上登录,有两种思路:
(1)后来的登录自动踢掉前面的登录。
(2)如果用户已经登录,则不允许后来者登录。
需要特别说明的是,项目的基础是已经是redis维护的session。

配置redisHttpSession

设置spring session由redis 管理。
2.1去掉yml中的http session 配置,yml和注解两者只选其一(同时配置,只有注解配置生效)。至于为什么不用yml,待会提到。

2.2 webSecurityConfig中加入注解@EnableRedisHttpSession

@EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 1700
        , flushMode = FlushMode.ON_SAVE)

登录后发现redis session namespace已经是我们命名的了

获取redis管理的sessionRepository

我们要限制一个用户的登录,自然要获取他在系统中的所有session。

2.再去查看springsSession官网的文档。springsession官网 提供文档https://docs.spring.io/spring-session/docs/   2.2.2.RELEASE/reference/html5/#api-findbyindexnamesessionrepository

SessionRepository实现也可以选择实现FindByIndexNameSessionRepository

FindByIndexNameSessionRepository提供一种方法,用于查找具有给定索引名称和索引值的所有会话

FindByIndexNameSessionRepository实现时,可以使用方便的方法查找特定用户的所有会话

/**
     * redis获取sessionRepository
     * RedisIndexedSessionRepository实现 FindByIndexNameSessionRepository接口
     */
    @Autowired
    //不加@Lazy这个会报什么循环引用...
    // Circular reference involving containing bean '.RedisHttpSessionConfiguration'
    @Lazy
    private FindByIndexNameSessionRepository<? extends Session> sessionRepository;

这里注意一点,当我通过yml配置redis session是,sessionRepository下面会有红线。

虽然不影响运行,但是强迫症,所以改用@EnableWebSecurity注解(至于为什么?我也不想知道…)。

将sessionRepository注入SpringSessionBackedSessionRegistry

是spring session为Spring Security提供的什么会话并发的会话注册表实现,大概是让springSecurity帮我们去限制登录,光一个sessionRepository是不行的,还得自己加点工具什么的。
webSecurityConfig加入:

/**
     * 是spring session为Spring Security提供的,
     * 用于在集群环境下控制会话并发的会话注册表实现
     * @return
     */
    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(){
        return new SpringSessionBackedSessionRegistry<>(sessionRepository);
    }

注:
https://blog.csdn.net/qq_34136709/article/details/106012825 这篇文章说还需要加一个HttpSessionEventPublisher来监听session销毁云云,大概是因为我用的是redis session吧,不需要这个,要了之后还会报错,啥错?我忘了。

新增一个session过期后的处理类

先创建一个CustomSessionInformationExpiredStrategy.java来处理session过期后如何通知前端的处理类,内容如下:

public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
        if (log.isDebugEnabled()) {
           log.debug("{} {}", event.getSessionInformation(), MessageConstant.SESSION_EVICT);
        }
        HttpServletResponse response = event.getResponse();
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        String responseJson = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.SESSION_EVICT, MessageConstant.SESSION_EVICT));
        response.getWriter().write(responseJson);
    }
}

注:一般都是自己重新写返回前端的信息,不会直接用框架抛出的错误信息

配置到configure(HttpSecurity http)方法上

.csrf().disable()
//登录互踢
.sessionManagement()
//在这里设置session的认证策略无效
//.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry()))
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(false) //false表示不阻止登录,就是新的覆盖旧的
//session失效后要做什么(提示前端什么内容)
.expiredSessionStrategy(new CustomSessionInformationExpiredStrategy()); 

注意:https://blog.csdn.net/qq_34136709/article/details/106012825 这篇文章说session认证的原理,我看到它是执行了一个session的认证策略,但是我debug对应的代码时,发现

这个session认证策略是NullAuthenticatedSessionStrategy,而不是它说的ConcurrentSessionControlAuthenticationStrategy。就是说我需要在哪里去配置这个session 认证策略。第一时间想到了configure(HttpSecurity http)里面配置

结果无效。之后看到别人的代码,想到这个策略应该是要在登录的时候加上去,而我们的登录一般都需要自己重写,自然上面的写法会无效。于是我找到了自定义的登录过滤器。


然后发现this.setSessionAuthenticationStrategy(sessionStrategy);确实存在。

public LoginFilter(UserVerifyAuthenticationProvider authenticationManager,
                       CustomAuthenticationSuccessHandler successHandler,
                       CustomAuthenticationFailureHandler failureHandler,
                       SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry) {
        //设置认证管理器(对登录请求进行认证和授权)
        this.authenticationManager = authenticationManager;
        //设置认证成功后的处理类
        this.setAuthenticationSuccessHandler(successHandler);
        //设置认证失败后的处理类
        this.setAuthenticationFailureHandler(failureHandler);
        //配置session认证策略(将springSecurity包装redis Session作为参数传入)
        ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new
                ConcurrentSessionControlAuthenticationStrategy(springSessionBackedSessionRegistry);
        //最多允许一个session
        sessionStrategy.setMaximumSessions(1);
        this.setSessionAuthenticationStrategy(sessionStrategy);
        //可以自定义登录请求的url
        super.setFilterProcessesUrl("/myLogin");
    }

启动 后就发现session认证策略已经改为我们设定的策略了。

完整的webSecurityConfig如下:

@Configuration
@EnableWebSecurity
//RedisFlushMode有两个参数:ON_SAVE(表示在response commit前刷新缓存),IMMEDIATE(表示只要有更新,就刷新缓存)
//yml和注解两者只选其一(同时配置,只有注解配置生效)
@EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 5000
        , flushMode = FlushMode.ON_SAVE)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserVerifyAuthenticationProvider authenticationManager;//认证用户类

    @Autowired
    private CustomAuthenticationSuccessHandler successHandler;//登录认证成功处理类

    @Autowired
    private CustomAuthenticationFailureHandler failureHandler;//登录认证失败处理类

    @Autowired
    private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回当前URL允许访问的角色列表
    @Autowired
    private MyAccessDecisionManager accessDecisionManager;//除登录登出外所有接口的权限校验

    /**
     * redis获取sessionRepository
     * RedisIndexedSessionRepository实现 FindByIndexNameSessionRepository接口
     */
    @Autowired
    //不加@Lazy这个会报什么循环引用...
    // Circular reference involving containing bean '.RedisHttpSessionConfiguration'
    @Lazy
    private FindByIndexNameSessionRepository<? extends Session> sessionRepository;

    /**
     * 是spring session为Spring Security提供的,
     * 用于在集群环境下控制会话并发的会话注册表实现
     * @return
     */
    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(){
        return new SpringSessionBackedSessionRegistry<>(sessionRepository);
    }

    /**
     * 密码加密
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(PasswordEncoder.class)
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置 HttpSessionIdResolver Bean
     * 登录之后将会在 Response Header x-auth-token 中 返回当前 sessionToken
     * 将token存储在前端 每次调用的时候 Request Header x-auth-token 带上 sessionToken
     */
    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
        return HeaderHttpSessionIdResolver.xAuthToken();
    }
    /**
     * Swagger等静态资源不进行拦截
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(
                "/*.html",
                "/favicon.ico",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js",
                "/error",
                "/webjars/**",
                "/resources/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v2/api-docs");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //配置一些不需要登录就可以访问的接口,这里配置失效了,放到了securityMetadataSource里面
                //.antMatchers("/demo/**", "/about/**").permitAll()
                //任何尚未匹配的URL只需要用户进行身份验证
                .anyRequest().authenticated()
                //登录后的接口权限校验
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(accessDecisionManager);
                        object.setSecurityMetadataSource(securityMetadataSource);
                        return object;
                    }
                })
                .and()
                //配置登出处理
                .logout().logoutUrl("/logout")
                .logoutSuccessHandler(new CustomLogoutSuccessHandler())
                .clearAuthentication(true)
                .and()
                //用来解决匿名用户访问无权限资源时的异常
                .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                //用来解决登陆认证过的用户访问无权限资源时的异常
                .accessDeniedHandler(new CustomAccessDeniedHandler())
                .and()
                //配置登录过滤器
                .addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler, sessionRegistry()))

                .csrf().disable()
                //登录互踢
                .sessionManagement()
                //在这里设置session的认证策略无效
                //.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry()))
                .maximumSessions(1)
                .sessionRegistry(sessionRegistry())
                .maxSessionsPreventsLogin(false) //false表示不阻止登录,就是新的覆盖旧的
                //session失效后要做什么(提示前端什么内容)
                .expiredSessionStrategy(new CustomSessionInformationExpiredStrategy());
        //配置头部
        http.headers()
                .contentTypeOptions()
                .and()
                .xssProtection()
                .and()
                //禁用缓存
                .cacheControl()
                .and()
                .httpStrictTransportSecurity()
                .and()
                //禁用页面镶嵌frame劫持安全协议  // 防止iframe 造成跨域
                .frameOptions().disable();
    }

}

其他

@Lazy
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;

至于这个不加@lazy会什么循环引用的问题,我就真的不想理会了。看了好长时间,都不知道谁和谁发生了循环引用。。。。。

到此这篇关于SpringSecurity整合springBoot、redis——实现登录互踢的文章就介绍到这了,更多相关SpringSecurity登录互踢内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于springboot和redis实现单点登录

    本文实例为大家分享了基于springboot和redis实现单点登录的具体代码,供大家参考,具体内容如下 1.具体的加密和解密方法 package com.example.demo.util; import com.google.common.base.Strings; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.crypto.Cipher; import javax.crypto.KeyG

  • 基于SpringBoot+Redis的Session共享与单点登录详解

    前言 使用Redis来实现Session共享,其实网上已经有很多例子了,这是确保在集群部署中最典型的redis使用场景.在SpringBoot项目中,其实可以一行运行代码都不用写,只需要简单添加添加依赖和一行注解就可以实现(当然配置信息还是需要的). 然后简单地把该项目部署到不同的tomcat下,比如不同的端口(A.B),但项目访问路径是相同的.此时在A中使用set方法,然后在B中使用get方法,就可以发现B中可以获取A中设置的内容. 但如果就把这样的一个项目在多个tomcat中的部署说实现了单

  • SpringBoot+SpringSession+Redis实现session共享及唯一登录示例

    最近在学习springboot,session这个点一直困扰了我好久,今天把这些天踩的坑分享出来吧,希望能帮助更多的人. 一.pom.xml配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency>

  • Spring Security OAuth2 实现登录互踢的示例代码

    本文主要介绍了Spring Security OAuth2 实现登录互踢的示例代码,分享给大家,具体如下: 背景说明 一个账号只能一处登录,类似的业务需求在现有后管类系统是非常常见的. 但在原有的 spring security oauth2 令牌方法流程(所谓的登录)无法满足类似的需求. 我们先来看 TokenEndpoint 的方法流程 客户端 带参访问 /oauth/token 接口,最后去调用 TokenGranter TokenGranter 根据不同的授权类型,获取用户认证信息 并去

  • SpringBoot+Vue+Redis实现单点登录(一处登录另一处退出登录)

    一.需求 实现用户在浏览器登录后,跳转到其他页面,当用户在其它地方又登录时,前面用户登录的页面退出登录(列如qq挤号那种方式) 二.实现思路 用户在前端填写用户信息登录后,后台接收数据先去数据库进行判断,如果登录成功,创建map集合,以用户id为键,token为值,先通过当前登录用户的id去获取token,如果token存在说明该用户已经登录过,调用redis以token为键删除上个用户的信息,调用方法生成新token,并将token存入map集合,将用户信息存入redis,并将token存入c

  • SpringSecurity整合springBoot、redis实现登录互踢功能

    背景 基于我的文章--<SpringSecurity整合springBoot.redis token动态url权限校验>.要实现的功能是要实现一个用户不可以同时在两台设备上登录,有两种思路: (1)后来的登录自动踢掉前面的登录. (2)如果用户已经登录,则不允许后来者登录. 需要特别说明的是,项目的基础是已经是redis维护的session. 配置redisHttpSession 设置spring session由redis 管理. 2.1去掉yml中的http session 配置,yml和

  • 教你使用springSecurity+jwt实现互踢功能

    jwt介绍:         JWT是一种用于双方之间传递安全信息的简洁的.URL安全的表述性声明规范.JWT作为一个开放的标准( RFC 7519 ),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息. jwt认证流程介绍: 1. 用户使用账号和面发出post请求:  2. 服务器使用私钥创建一个jwt:  3. 服务器返回这个jwt给浏览器:  4. 浏览器将该jwt串在请求头中像服务器发送请求:  5. 服务器验证该jwt:  6. 返回响应的资源给浏览器.

  • SpringBoot与SpringSecurity整合方法附源码

    依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Thymeleaf --> <dependency> <groupId>org.thymeleaf<

  • SpringBoot整合SpringSession实现分布式登录详情

    目录 Session 共享 为什么服务器 A 登录后,请求发到服务器 B,不认识该用户? 解决方案 SpringBoot整合SpringSession实现分布式登录 Session 共享 比如两个域名: aaa.yupi.com bbb.yupi.com 如果要共享 cookie,可以种一个更高层的公共域名,比如 yupi.com 为什么服务器 A 登录后,请求发到服务器 B,不认识该用户? 用户在 A 登录,所以 session(用户登录信息)存在了 A 上 结果请求 B 时,B 没有用户信息

  • Redis整合SpringBoot的RedisTemplate实现类(实例详解)

    Redis整合SpringBoot>>RedisService 接口 package com.tuan.common.base.redis; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; public interface RedisService { //Redis 字符串(String) /** * 模糊值再删除 * @param

  • springboot+jwt+springSecurity微信小程序授权登录问题

    场景重现:1.微信小程序向后台发送请求 --而后台web采用的springSecuriry没有token生成,就会拦截请求,,所以小编记录下这个问题 微信小程序授权登录问题 思路 参考网上一大堆资料 核心关键字: 自定义授权+鉴权 (说的通俗就是解决办法就是改造springSecurity的过滤器) 参考文章 https://www.jb51.net/article/204704.htm 总的来说的 通过自定义的WxAppletAuthenticationFilter替换默认的UsernameP

  • springboot整合shiro多验证登录功能的实现(账号密码登录和使用手机验证码登录)

    1. 首先新建一个shiroConfig shiro的配置类,代码如下: @Configuration public class SpringShiroConfig { /** * @param realms 这儿使用接口集合是为了实现多验证登录时使用的 * @return */ @Bean public SecurityManager securityManager(Collection<Realm> realms) { DefaultWebSecurityManager sManager

  • Springboot框架整合添加redis缓存功能

    目录 一:安装Redis 二:添加Redis依赖 三:添加Redis配置信息 四:创建RedisConfigurer 五:创建Redis常用方法 六:接口测试 Hello大家好,本章我们添加redis缓存功能 .另求各路大神指点,感谢 一:安装Redis 因本人电脑是windows系统,从https://github.com/ServiceStack/redis-windows下载了兼容windows系统的redis 下载后直接解压到自定义目录,运行cmd命令,进入到这个文件夹,在这个文件夹下运

  • SpringBoot整合Keycloak实现单点登录的示例代码

    目录 1. 搭建Keycloak服务器 2. 配置权限 2.1. 登陆 2.2. 创建Realm 2.3. 创建用户 2.4. 创建客户端 2.5. 创建角色 2.6. 配置用户角色关系 2.7. 配置客户端和角色关系 3. 整合SpringBoot 3.1. 引入核心依赖 3.2. 编写Controller 3.3. 编写application.yml 4. 验证 Keycloak是一个开源的身份和权限访问管理工具,轻松为应用程序和安全服务添加身份验证,无需处理储存用户或者验证用户,其提供用户

随机推荐