SpringSecurity(九)【会话管理】

九、会话管理


简介

当浏览器调用登录接口登录成功之后,服务端会和浏览器之间创建一个会话(Session),浏览器在每次发送请求时都会携带一个 SessionId,服务端则根据这个 SessionId 来判断用户身份。当浏览器关闭之后,服务端的 Session 并不会自动销毁,需要开发者手动在服务端调用 Session 销毁方法,或者等 Session 过期时间到了自动销毁。在 Spring Security 中,与 HttpSession 相关的功能由 SessionManagementFilter 和 SessionAuthenticationStrategy 接口来处理,Session 相关操作委托给 SessionAuthenticationStrategy 接口去完成

9.1 会话并发管理

简介

会话并发管理就是指在当前系统中,同一个用户可以同时创建多少个会话,如果一个设备对应一个会话,那么也可以简单理解为同一个用户可以同时在多台设备上进行登录。默认情况下,同一用户在多少台设备上登录并没有限制,不过开发者可以在 SpringSecurity 中对此进行配置

  • 代码配置
package com.vinjcent.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.session.HttpSessionEventPublisher;

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                .disable()
                .sessionManagement()    // 开启会话管理
                .maximumSessions(1);    // 允许会话最大并发只能一个客户端
    }
    
    // 用于监听会话的创建和销毁
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}
  • 使用两个浏览器进行登录,观察一个登录之后,在进行另外一个登录,前者再次访问系统资源将会被提醒该用户正在被使用

在这里插入图片描述

  1. sessionManagement() 用来开启会话管理、sessionManagement() 用来指定会话的并发数
  2. HttpSessionEventPublisher 提供一个 HttpSessionEventPublisher 实例。SpringSecurity 中通过一个 Map 集合来维护当前的 HttpSession 记录,进而实现会话的并发管理。当用户登陆成功时,就向集合中添加一条 HttpSession 记录;当会话销毁时,就从集合中移除一条 HttpSession 记录。HttpSessionEventPublisher 实现了 HttpSessionListener 接口,可以监听到 HttpSession 的创建和销毁事件,并将 HttpSession 的 创建/销毁 事件发布出去,这样,当有 HttpSession 销毁时,SpringSecurity 就可以感知到该事件了

9.2 会话失效

传统 web 开发处理

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
                ...
                .sessionManagement()    // 开启会话管理
                .maximumSessions(1)     // 允许会话最大并发只能一个客户端
                .expiredUrl("/toLogin");        // 会话过期处理
    }

前后端分离

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .csrf()
            .disable()
            .sessionManagement()    // 开启会话管理
            .maximumSessions(1)     // 允许会话最大并发只能一个客户端
            // .expiredUrl("/toLogin");        // 传统架构的会话过期处理方案
            .expiredSessionStrategy(event -> {      // 前后端分离架构会话过期处理方案
                HttpServletResponse response = event.getResponse();
                HashMap<String, Object> result = new HashMap<>();
                result.put("status", 500);
                result.put("msg", "当前会话已经失效,请重新登录");
                String str = new ObjectMapper().writeValueAsString(result);
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().println(str);
                response.flushBuffer();
            });
}
  • 测试访问

在这里插入图片描述

9.3 禁止再次登录

默认的效果是一种被 “挤下线” 的效果,后面登录的用户会把前面登录的用户 “挤下线”。还有一种禁止后来者登录,即一旦当前用户登陆成功,后来者无法再次使用相同的用户登录,直到当前用户主动注销登录,配置如下

在这里插入图片描述

  • 代码配置
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .logout()   // 需要开启退出登录
            .and()
            .csrf()
            .disable()
            .sessionManagement()    // 开启会话管理
            .maximumSessions(1)
            // .expiredUrl("/toLogin")
            .expiredSessionStrategy(event -> {
                HttpServletResponse response = event.getResponse();
                HashMap<String, Object> result = new HashMap<>();
                result.put("status", 500);
                result.put("msg", "当前会话已经失效,请重新登录");
                String str = new ObjectMapper().writeValueAsString(result);
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().println(str);
                response.flushBuffer();
            })
            .maxSessionsPreventsLogin(true);    // 一旦登录,禁止再次登录
}

9.4 会话共享

前面所有的会话管理都是单机上的会话管理,如果当前是集群环境,前面所讲的会话管理方案就会失效。此时可以利用 spring-session 结合 redis 实现 session 共享

  • 实战
    1. 引入依赖pom.xml
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--session-redis-->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
  1. 配置application.yml文件
# 端口号
server:
  port: 3035
  servlet:
    session:
      # 设置session过期时间
      timeout: 1
# 服务应用名称
spring:
  application:
    name: SpringSecurity10security
  # The Redis settings
  redis:
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379

# 日志处理,为了展示 mybatis 运行 sql 语句
logging:
  level:
    com:
      vinjcent:
        debug
  1. 配置 Security 适配器
  • WebSecurityConfigurerAdapter
package com.vinjcent.config.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    // 注入 session 仓库
    private final FindByIndexNameSessionRepository sessionRepository;

    @Autowired
    public WebSecurityConfiguration(FindByIndexNameSessionRepository sessionRepository) {
        this.sessionRepository = sessionRepository;
    }

    // 注册 session 同步到 redis 中
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry(sessionRepository);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .successForwardUrl("/test")
                .and()
                .logout()   // 需要开启退出登录
                .and()
                .csrf()
                .disable()
                .sessionManagement()    // 开启会话管理
                .maximumSessions(1)
                // .expiredUrl("/toLogin");
                .expiredSessionStrategy(event -> {
                    HttpServletResponse response = event.getResponse();
                    HashMap<String, Object> result = new HashMap<>();
                    result.put("status", 500);
                    result.put("msg", "当前会话已经失效,请重新登录");
                    String str = new ObjectMapper().writeValueAsString(result);
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().println(str);
                })
                .maxSessionsPreventsLogin(true)    // 一旦登录,禁止再次登录
                .sessionRegistry(sessionRegistry());     // 将 session 交给谁管理,前后端分离自定义过滤器需要配setSessionAuthenticationStrategy
    }
}
  1. 将 redis 作为全局 HttpSession(不开启redis作为HttpSession会使得前面的配置失效,导致出现每个服务有各自的 session 情况
package com.vinjcent.config.redis;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration
@EnableRedisHttpSession // 将整个应用中使用session的数据全部交给redis处理
public class RedisSessionManager {

}
  1. 模拟分布式服务
  • 将当前的服务复制一份运行,并设置运行vm参数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  1. 启动本地 redis-server 服务,测试两个服务进行登录操作,结果如图所示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naijia_OvO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值