一.项目搭建
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.huang</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>test</name>
<description>test</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.security.user.name=zhangsan
spring.security.user.password=123
server.port=8081
HelloController
package com.huang.test;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
SecurityConfig关键,过滤器配置在下文
二.配置会话管理
2.1过滤器链配置会话管理
默认情况下账户登录数量没有限制,但是有的业务需求一个账户只能登录一台设备,例如qq,那我们应该怎么做呢?
对,没错,继续配置我们的SecurityConfig里的过滤器链,securityFilterChain
package com.huang.test.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.web.SecurityFilterChain;
import org.springframework.security.web.session.HttpSessionEventPublisher;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
//SessionManagementFilter
.sessionManagement()
//设置会话的最大并发数,设置一个用户只可以在一台设备上登录
//默认情况下,达到最大会话并发数之后,会挤掉上一次的登录
.maximumSessions(1)
//达到最大会话数的时候,是否要阻止下一个登录(自己设置登录)
.maxSessionsPreventsLogin(true)
.and().and()
.csrf().disable();
return http.build();
}
}
2.2 配置会话管理的关键代码(加入过滤器链)
关键代码
注意最下面是两个.and()为了保证返回的值是HttpSession
.and()
//SessionManagementFilter
.sessionManagement()
//设置会话的最大并发数,设置一个用户只可以在一台设备上登录
.maximumSessions(1)
//达到最大会话数的时候,是否要阻止下一个登录,
.and().and()
2.3 会话管理测试为什么要用两个浏览器
测试.(用两个浏览器登录,因为用一个浏览器开两个窗口会有cookie缓存,看似是一个用户登录两次,其实跟你同一个电脑登录qq,然后把qq下了,又在这个电脑上登录qq一样,而开两个浏览器登录qq就相当于两个电脑登录一个qq,这样才能看出来测试结果)
浏览器一:
浏览器二:
再次登录浏览器一:
如上所示,实现了会话管理
2.4 .and().and()的原因
上面注意一个关键:加两个.and()
在这里需要两个.and()保证返回的是HttpSecurity,因为前面配置完了最大session数,还可以配置session的策略,(这个策略是t掉之前的用户如qq,还是禁止登录)Spring Security默认的会话管理策略是t掉上一次的登录
2.5 默认会话管理策略和策略配置
先登录浏览器1用户,再登录浏览器二用户,刷新浏览器一,报出
Spring Security默认的会话管理策略是t掉上一次的登录,当然我们也可以去调整我们的策略如下图:
2.5.1 默认会话管理策略配置与测试
2.5.2 禁止登录会话管理策略配置与测试
securityFilterChain关键代码:
//SessionManagementFilter
.sessionManagement()
//设置会话的最大并发数,设置一个用户只可以在一台设备上登录
//默认情况下,达到最大会话并发数之后,会挤掉上一次的登录
.maximumSessions(1)
//达到最大会话数的时候,是否要阻止下一个登录
.maxSessionsPreventsLogin(true)
.and().and()
浏览器一:登录浏览器一
浏览器二:试图登录浏览器二,报出
登录完之后,再登录新的是没有办法并且会给出提示的,那我们想要登录后再去登录怎么办?把第一个用户注销试试,出现如下图
浏览器1注销登录
浏览器2登录报错
还是没办法继续登录,那这是为什么?
2.5.3 为什么禁止登录策略,注销了浏览器一的用户后还是没办法再第二个浏览器登录
这是为什么?
涉及到了会话管理的实现原理
在服务端,spring Security维护了一个会话注册表,所有登录上来的用户,都会注册到这个会话注册表中所谓的会话注册表,本质是一个Map集合,Map的key是当前用户对象!注意,是当前用户的对象哦,Map的value是一个集合,这个集合中保存着这个用户的所有会话。如下结构Map<User,List>(这里存储的session是包装后的session).这个value对应着这个用户的所有会话session,每次登录后就能够去Map里面拿出来这个用户的所有会话然后判断一下(set)就知道该不该登录了,当用户注销登录的时候,用户的session会被自动销毁,但是Map中的List集合中的session并不会自动移除,所以,就导致每次登录的时候都会判断为session已经登录,所以我们应当在用户注销登录的时候,将list集合中把用户对应的会话session移除掉,
具体操作步骤
如下所示:
securityConfig中加入配置–监听HttpSession的销毁操作
这个监听器的作用:
这个事件,可以监听到 HttpSession 的销毁操作,当有 HttpSession 销毁的时候,就将这个销毁的事件广播出去,Map收到通知,就会从value即list集合中把对应的会话session给移除
/**
* 这个事件,可以监听到 HttpSession 的销毁操作,当有 HttpSession 销毁的时候,就将这个销毁的事件广播出去
* @return
*/
@Bean
HttpSessionEventPublisher sessionEventPublisher() {
return new HttpSessionEventPublisher();
}
加了监听HttpSession的销毁操作的配置HttpsessionEventPublisher之后的测试:
浏览器一:
浏览器二:登录成功