Spring Security实战(四)—— 会话管理

目录

一、理解会话

二、防御会话固定攻击

 三、会话过期

四、会话并发控制

五、集群会话的缺陷

六、集群会话的解决方案

七、整合Spring Session解决集群会话问题


         只需在两个浏览器中用同一个账号登录就会发现,系统并没有任何会话并发限制。一个账号可以多处同时登陆并不是一个好的策略。Spring Security已经为我们提供了完善的会话管理功能,包括会话固定攻击、会话超时检测以及会话并发控制。

一、理解会话

        会话(session)就是无状态的HTTP实现用户状态可维持的一种解决方案。HTTP本身的无状态使得用户在与服务器的交互过程中,每个请求之间都没有关联性。这就意味着用户的访问没有身份记录,站点也无法为用户提供个性化的服务。session的诞生解决了这个难题。

        服务器通过与用户约定每个请求都携带一个id类的信息,从而让不同的请求之间有了关联。当用户首次访问系统时,系统会为该用户生成一个sessionId,并添加到cookie中。在该用户的会话期内,每个请求都自动携带该cookie,因此系统可以很轻易识别出是来自哪个用户的请求。

会话固定攻击:

        黑客只需访问一次系统,将系统生成的sessionId提取并拼凑在URL上,然后将该URL发给一些取得信任的用户。只要用户在session有效期内通过此URL进行登录,该sessionId就会绑定用户的身份,黑客便可以轻松享有同样的会话状态,完全不需要用户名和密码。这就是会话固定攻击。

二、防御会话固定攻击

         防御会话固定攻击的方法非常简单,只需在用户登录之前重新生成新的session即可,在继承WebSecurityConfigureAdapter 时,Spring Security 已经启用了该配置。

        sessionManagement是一个会话管理的配置器,其中,防御会话固定攻击的策略有四种:

  • none:不做任何变动,登录之后沿用旧的session
  • newSession:登录之后创建一个新的session
  • migrateSession:登录之后创建一个新的session,并将旧的session中的数据复制过来
  • changeSessionId:不创建新的会话,而是使用由Servlet容器提供的会话固定保护

默认启用的是migrateSession策略,可以自己根据需要修改,如:

 

 三、会话过期

        除防御会话固定攻击外,还可以通过Spring Security配置一些会话过期策略,例如过期时跳转到某个URL:

 也可以完全自定义过期策略:

public class MyInvalidSessionStrategy implements InvalidSessionStrategy {
    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write("session无效,已过期!");
    }
}

四、会话并发控制

        设置单个用户允许同时在线的最大会话数,如果没有额外的配置,新登录的会话会踢掉旧的会话。

 查看效果:

在另一个浏览器登录之后,之前的浏览器会提示出这条信息。改成 maximumSession(2) 时候,两个浏览器可以同时登录。

五、集群会话的缺陷

        大多数集群部署会采用类似下图的网络结构:

        在这种网络结构下,用户的请求首先会到LB(负载均衡)服务器上,LB服务器再根据负载均衡策略将这些请求转发至后面的服务,以达到请求分散的目的。正常情况下,在集群中,同个用户的请求可能会分发到不同的服务器上,加入登录操作是在Server1上完成的,Server1缓存了用户的登录状态,但Server2和Server0并不知情,如果用户的后续操作被分配到了Server2或Server0上,这时就会要求该用户重新登录,这就是典型的会话状态集群不同步问题。

六、集群会话的解决方案

        集群会话的常见方案有三种:

  • session保持——将相同客户的请求转发至相同的而服务器上
  • session复制——集群服务器之间同步session数据
  • session共享——将session存储到独立的服务器,进行共享,在内网环境下,高可用部署的Redis服务器是最优选择。Redis基于内存的特性让它拥有极高的读写性能,高可用部署不仅降低了网络I/O损耗,还提高了稳定性。

七、整合Spring Session解决集群会话问题

(1)添加依赖

        <!--spring session核心依赖-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>2.4.2</version>
        </dependency>

        <!--springboot 整合redis核心依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- 使用Jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.6.0</version>
        </dependency>

 (2)配置Spring Session

为Spring Security提供集群支持的会话注册表

@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig {
  
    // 提供redis连接,localhost:6379
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new JedisConnectionFactory();
    }
  
    @Bean
    public SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry(
            FindByIndexNameSessionRepository sessionRepository) {
        return new SpringSessionBackedSessionRegistry(sessionRepository);
    }
  
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

    

在该类中,使用了@Autowired注解将RedisConnectionFactory对象注入进来。RedisConnectionFactory对象用于创建一个RedisTemplate对象,用于操作Redis存储session的数据。

@Bean注解被用于定义了三个Bean:

  1. sessionRedisOperations方法返回一个RedisTemplate对象,用于操作Redis存储session的数据。

  2. sessionRepository方法返回一个FindByIndexNameSessionRepository对象,这是Spring Session提供的一个实现类,用于管理HttpSession。

  3. sessionRepository方法还返回了一个MapSessionRepository对象,这个对象用于在没有Redis环境的情况下,将Session存储在内存中。

在这个类中,配置了Redis存储session和使用内存存储session的两种实现,可以根据需要选择相应的实现。

需要注意的是,如果想使用Redis存储session,就必须保证已经正确配置了Redis环境,包括Redis服务器的地址和端口等信息。

(3)将新的会话注册表提供给Spring Security

 (4)在不同的端口上启动服务测试

默认的服务是启动在8080端口的,这里我们在8081端口上再启动一个:

 两个端口都启动后,先访问8080端口,再访问8081端口:

 这时候再刷新8080端口的页面,可以看到已经被8081上的用户给挤掉了:

 在redis中也可以看到存储的session信息:

猜你喜欢

转载自blog.csdn.net/weixin_49561506/article/details/130180403