SpringSecurity(05)——会话管理

会话管理

http.sessionManagement()

  1. invalidSessionUrl(String invalidSessionUrl):指定会话失效时(请求携带无效的 JSESSIONID 访问系统)重定向的 URL,默认重定向到登录页面。
  2. invalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy):指定会话失效时(请求携带无效的 JSESSIONID 访问系统)的处理策略。
  3. maximumSessions(int maximumSessions):指定每个用户的最大并发会话数量,-1 表示不限数量。
  4. maxSessionsPreventsLogin(boolean maxSessionsPreventsLogin):如果设置为 true,表示某用户达到最大会话并发数后,新会话请求会被拒绝登录;如果设置为 false,表示某用户达到最大会话并发数后,新会话请求访问时,其最老会话会在下一次请求时失效并根据 expiredUrl() 或者 expiredSessionStrategy() 方法配置的会话失效策略进行处理,默认值为 false。
  5. expiredUrl(String expiredUrl):如果某用户达到最大会话并发数后,新会话请求访问时,其最老会话会在下一次请求时失效并重定向到 expiredUrl。
  6. expiredSessionStrategy(SessionInformationExpiredStrategy expiredSessionStrategy):如果某用户达到最大会话并发数后,新会话请求访问时,其最老会话会在下一次请求中失效并按照该策略处理请求。注意如果本方法与 expiredUrl() 同时使用,优先使用 expiredUrl() 的配置。
  7. sessionRegistry(SessionRegistry sessionRegistry):设置所要使用的 sessionRegistry,默认配置的是 SessionRegistryImpl 实现类
  8. sessionCreationPolicy
  • SessionCreationPolicy.ALWAYS:总是创建HttpSession
  • SessionCreationPolicy.IF_REQUIRED:SpringSecurity只会在需要时创建一个HttpSession
  • SessionCreationPolicy.NEVER:SpringSecurity不会创建HttpSession,但如果他已经存在,将可以使用HttpSession
  • SessionCreationPolicy.STATELESS:SpringSecurity永远不会创建HttpSession,他不会使用HttpSession来获取SecurityContext

会话失效处理

当用户的session会话失败(请求携带着无效的JSESSIONID访问系统)时,可以制定相关策略对会话失效的请求进行处理

invalidSessionUrl

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 定制基于 HTTP 请求的用户访问控制
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .and()
            .authorizeRequests()
            .anyRequest()
            .authenticated();
        
        // 开启 Session 会话管理配置
        http.sessionManagement()
                // 设置 Session 会话失效时重定向路径,默认为 loginPage()
                .invalidSessionUrl("/login/page");        
    }
}
# session 失效时间,单位是秒,默认为 30min
server.servlet.session.timeout=30m

# JSESSIONID (Cookie)的生命周期,单位是秒,默认为 -1
server.servlet.session.cookie.max-age=-1

注意:Session 的失效时间至少要 1 分钟,少于 1 分钟按照 1 分钟配置

invalidSessionStrategy

/**
 * 用户请求携带无效的 JSESSIONID 访问时的处理策略,即对应的 Session 会话失效
 */
@Component
public class CustomInvalidSessionStrategy implements InvalidSessionStrategy {

    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 清除浏览器中的无效的 JSESSIONID
        Cookie cookie = new Cookie("JSESSIONID", null);
        cookie.setPath(getCookiePath(request));
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        String xRequestedWith = request.getHeader("x-requested-with");
        // 判断前端的请求是否为 ajax 请求
        if ("XMLHttpRequest".equals(xRequestedWith)) {
            // 响应 JSON 数据
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write("SESSION 失效,请重新登录!");
        }else {
            // 重定向到登录页面
            redirectStrategy.sendRedirect(request, response, "/login/page");
        }
    }

    private String getCookiePath(HttpServletRequest request) {
        String contextPath = request.getContextPath();
        return contextPath.length() > 0 ? contextPath : "/";
    }
}
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    // 自定义 Session 会话失效策略
    @Autowired
    private CustomInvalidSessionStrategy invalidSessionStrategy;  

    /**
     * 定制基于 HTTP 请求的用户访问控制
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .and()
            .authorizeRequests()
            .anyRequest()
            .authenticated();
        
        // 开启 Session 会话管理配置
        http.sessionManagement()
                // 设置 Session 会话失效时重定向路径,默认为 loginPage()
                // .invalidSessionUrl("/login/page")
                // 配置使用自定义的 Session 会话失效处理策略
                .invalidSessionStrategy(invalidSessionStrategy);       
    }
}

会话并发控制

限制用户二次登录

如果同一个用户在第二个地方登录,则不允许他二次登录

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 定制基于 HTTP 请求的用户访问控制
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .and()
            .authorizeRequests()
            .anyRequest()
            .authenticated();
        
        // 开启 Session 会话管理配置
        http.sessionManagement()
                // 配置使用自定义的 Session 会话失效处理策略
                .invalidSessionStrategy(invalidSessionStrategy)
                // 设置单用户的 Session 最大并发会话数量,-1 表示不受限制
                .maximumSessions(1)
                // 设置为 true,表示某用户达到最大会话并发数后,新会话请求会被拒绝登录
                .maxSessionsPreventsLogin(true);      
                // 设置所要使用的 sessionRegistry,默认为 SessionRegistryImpl 实现类
                .sessionRegistry(sessionRegistry());
    }
    
    /**
     * 注册 SessionRegistry,该 Bean 用于管理 Session 会话并发控制
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
    
    /**
     * 配置 Session 的监听器(注意:如果使用并发 Sessoion 控制,一般都需要配置该监听器)
     * 解决 Session 失效后, SessionRegistry 中 SessionInformation 没有同步失效的问题
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

踢人下线

如果同一个用户在第二个地方登录,则将第一个踢下线

/**
 * 前提:Session 并发处理的配置为 maxSessionsPreventsLogin(false)
 * 用户的并发 Session 会话数量达到上限,新会话登录后,最老会话会在下一次请求中失效,并执行此策略
 */
@Component
public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {

    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
        HttpServletRequest request = event.getRequest();
        HttpServletResponse response = event.getResponse();

        // 最老会话被踢下线时显示的信息
        UserDetails userDetails = (UserDetails) event.getSessionInformation().getPrincipal();
        String msg = String.format("用户[%s]在另外一台机器登录,您已下线!", userDetails.getUsername());

        String xRequestedWith = event.getRequest().getHeader("x-requested-with");
        // 判断前端的请求是否为 ajax 请求
        if ("XMLHttpRequest".equals(xRequestedWith)) {
            // 认证成功,响应 JSON 数据
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(msg);
        }else {
            // 返回到登录页面显示信息
            AuthenticationException e = new AuthenticationServiceException(msg);
            request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", e);
            redirectStrategy.sendRedirect(request, response, "/login/page?error");
        }
    }
}
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomSessionInformationExpiredStrategy sessionInformationExpiredStrategy;// 自定义最老会话失效策略

    /**
     * 定制基于 HTTP 请求的用户访问控制
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 开启 Session 会话管理配置
        http.sessionManagement()
                // 配置使用自定义的 Session 会话失效处理策略
                .invalidSessionStrategy(invalidSessionStrategy)
                // 设置单用户的 Session 最大并发会话数量,-1 表示不受限制
                .maximumSessions(1)
                // 设置为 true,表示某用户达到最大会话并发数后,新会话请求会被拒绝登录;
                // 设置为 false,表示某用户达到最大会话并发数后,新会话请求访问时,其最老会话会在下一次请求时失效
                .maxSessionsPreventsLogin(false)
                // 设置所要使用的 sessionRegistry,默认为 SessionRegistryImpl 实现类
                .sessionRegistry(sessionRegistry())
                // 最老会话在下一次请求时失效,并重定向到 /login/page
                //.expiredUrl("/login/page");
                // 最老会话在下一次请求时失效,并按照自定义策略处理
                .expiredSessionStrategy(sessionInformationExpiredStrategy);
    }

    /**
     * 注册 SessionRegistry,该 Bean 用于管理 Session 会话并发控制
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
     
    /**
     * 配置 Session 的监听器(如果使用并发 Sessoion 控制,一般都需要配置)
     * 解决 Session 失效后, SessionRegistry 中 SessionInformation 没有同步失效问题
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }   
}

会话集群控制


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-pool2artifactId>
    <version>2.8.0version>
dependency>

<dependency>
    <groupId>org.springframework.sessiongroupId>
    <artifactId>spring-session-data-redisartifactId>
dependency>
# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码(默认无)
spring.redis.password=
# Redis数据库索引(默认为0)
spring.redis.database=1
# 连接池最大连接数(使用负值表示没有限制),默认 8
spring.redis.lettuce.pool.max-active=100
# 连接池大阻塞等待时间(使用负值表示没有限制),默认 -1
spring.redis.lettuce.pool.max-wait=PT10S
# 连接池中的大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=10
# 连接池中的小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=1
# 连接超时时间
spring.redis.timeout=PT10S

# 使用 Redis 存储 Session,默认为 none(使用内存存储)
spring.session.store-type=redis
# 指定存储 SessionId 的 Cookie 名(使用 Redis 存储 Session 后,Cookie 名默认会变为 SESSION)
server.servlet.session.cookie.name=JSESSIONID

Redis 存储 Session 默认的序列化方式为 JdkSerializationRedisSerializer,所以存入 Session 的对象都要实现 Serializable 接口

你可能感兴趣的:(SpringSecurity,spring,java)