本章节就学习Spring Security会话的应用。例如,配置会话并发数、会话固定攻击与防御、Session共享等等。
会话并发管理会话并发管理是指当前用户可以在系统存在多少会话,默认情况,会话是没有限制。当然我们配置一个用户只能有一个会话,这样就可以显示踢人下线。
配置基于前面配置改造,这里主要罗列最主要的配置代码
@Configuration
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.defaultSuccessUrl("/hello1")
.permitAll()
.and()
.csrf()
.disable()
.sessionManagement().maximumSessions(1).expiredUrl("/login");
}
// 增加session发布者:创建、销毁都发布事件
@Bean
HttpSessionEventPublisher sessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
这里配置最大会话数为1,当会话过期时,会重定向到 /login 地址。我们可以通过 maxSessionsPreventsLogin() 配置禁止后来者登录。注意:这里我们需要重写自定义用户类 User 的 hashCode() 方法和 equals() 方法。
原理 SessionInfomationSessionInformation 类是用来记录会话记录的。
public class SessionInformation implements Serializable {
private static final long serialVersionUID = 510L;
private Date lastRequest;
private final Object principal; // 表示用户
private final String sessionId; // 会话id
private boolean expired = false;
// getter and setter
}
SessionRegistry
SessioinRegistry 接口是用来保证会话的,当用户登录成功时,需要把会话保存起来。SessionRegistry 只有一个实现类 SessionRegistryImpl。它怎么来存储会话的呢?
public class SessionRegistryImpl implements SessionRegistry, ApplicationListenerSessionAuthenticationStrategy{ // 一个用户可以有多个会话id private final ConcurrentMap
SessionAuthenticationStrategy 接口用来定义如何去认证 session,例如 session 是否过期,会话数量是否超过规定等等。SessionAuthenticationStrategy 接口有7个实现类,这里是用到 策略设计模式 和 代理设计模式
踢人下线流程图大概逻辑是:根据当前用户查询用户的会话记录表,判断会话记录表记录个数是否等于系统配置会话数。如果小于的话,说明此时会话并发说还没有达到,可以进行登录;否则,需要判断当前会话是否在会话记录表中存在,如果存在的话,说明是旧的会话;否则此时这个会话是新的会话,系统需要把会话记录表中最近最少使用的会话设置为过期。此时过滤器会来到 ConcurrentSessionFilter 过滤器。过滤器会调用 getSessionInformation() 根据当前会话Id查询会话记录,判断会话记录是否过期;如果没有过期,更新时间;否则,删除会话记录表一项,清空上下文,然后重定向到指定url
会话固定攻击与防御 什么是会话固定攻击会话固定攻击:攻击者首先登录网站A,然后可以得到一个会话ID,然后攻击者构造一个带有会话ID的URL,让用户访问。当用户访问并登录后,攻击者就获取用户登录权限,可以以用户的身份访问。
会话固定攻击的防御策略Spring Security提供一下几个策略:
- Spring Security自带防火墙,可以拦截带有sessionId的请求。
- Spring Security自带 ChangeSessionIdAuthenticationStrategy 策略,在用户登录后,改变会话ID。
- 在Http响应设置 cookie 的 http-only 属性,防止XSS攻击读取cookie
Spring Security 的配置方式如下所示:
http.sessionManagement().sessionFixation().changeSessionId();
- changeSessionId():用户登录成功后,修改sessionId,默认就是此策略,实现类为 ChangeSessionIdAuthenticationStrategy
- none() :用户登录成功后,不做任何变化,实现类为 NullAuthenticatedSessionStrategy
- migrateSession():用户登录成功后,创建新的session,并把旧的Session的属性拷贝到新的Session中,实现类为 SessionFixationProtectionStrategy
- newSession():用户登录成功,创建新的Session,并把旧的Session的以 SPRING_SECURITY_ 开头的属性拷贝到新的属性,实现类也是 SessionFixationProtectionStrategy ,这里将 migrateSessionAttributes 属性设置为 false
在集群环境下,每个tomcat实例都会通过Spring Security维护自己的会话表,这样配置去管理会话并发的话,那必然会出现用户依赖可以在多个tomcat实例进行登录。
集群会话解决办法一般解决集群会话有三个办法:
-
Session复制:多个服务器之间复制Session信息,这样每个服务器都有所有Session信息,Tomcat通过IP组播对这种办法提供支持。缺点是:占用宽带、有延时、服务数量越多越低效。
-
Session粘性:在Nginx上通过一致性hash,将相同的请求总是分发到服务器上。这样方案可以解决一部分集群Session问题,但是无法解决集群Session会话管理问题。
-
Session共享:通过Redis来存储集群所有Session。
会话固定漏洞的一点学习、分析与思考



