前言
会话固定攻击:利用服务器会话不变机制,借他人之手获取认证和授权,然后冒充他人。举例如下:
1.A先打开一个网站 "http://unsafe",然后服务器会回复他一个session id。比session id=mjg4qid0wioq, Mallory把这个id记下了;
2.A给B发送一个电子邮件,他假装是银行在宣传自己的新业务,例如,我行推出了一项新服务,率先体验请点击:"http://unsafe/?sessionId=mjg4qid0wioq",sessionId后面是A自己的session id;
3.Alice被吸引了,点击了"http://unsafe/?sessionId=mjg4qid0wioq",像往常一样,输入了自己的帐号和口令从而登录到银行网站;
4.因为服务器的session id不改变,现在A点击"http://unsafe/?SID=mjg4qid0wioq"后,他就拥有了B的身份。可以为所欲为了。
图例:
session fixation防护
spring security默认是开通session fixation防护的,如果想显示开通,可以通过以下代码实现:
http.sessionManagement().sessionFixation().migrateSession()
spring 防护的原理为,每当用户认证过后,就会重新生成一个新的session,并抛弃旧的session,下图是spring security的防护原理:
从上图中可知,SessionManagementFilter负责检查一个用户是否是新认证的用户,如果是则会调用接口SessionAuthenticationStrategy进行处理,SessionAuthenticationStrategy的实现类SessionFixationProtectionStrategy会为用户创建一个新的session 同时丢弃旧的session。
spring security session的几个方法选项:
none() 关闭spring security的session防护功能,spring不会配置SessionManagementFilter类;
migrateSession() 用户认证之后,会重新创建一个新的session,并且将旧session中的属性,迁移到新的session中;
newSession()用户认证之后,会新创建一个session,但是不会将旧的session中的属性,迁移到新的session中
单用户并发session控制
确保单个用户的单个账号,只有一个活跃的session,这也是一个常见的需求,先看下在Security.java中的代码配置:
http.sessionManagement().maximumSessions(1)
@Bean
public HttpSessionEventPublisher (){
new HttpSessionEventPublisher();
}
并发控制的原理
Spring security使用SessionRegistry来维护一个列表,列表记录了每一个活跃的session以及与之相关的认证用户,当session的生命周期发生变化时,容器会产生一个HttpSessionEventPublisher事件,SessionRegistry捕获到HttpSessionEventPublisher事件,并实时地更新列表。当用户访问一个受保护的站点时,SessionManagerFilter会通过SessionRegistry检查用户的激活session是否存在,若不存在,则将SessionRegistry中保存的激活session置为无效。另外,ConcurrentSessionFilter会识别session是否过期,若session过期则更新SessionRegistry。session的过期事件可能是由服务器产生,也可能是由ConcurrentSessionControlStrategy强制设置为过期。原理图如下所示:
配置session过期重定位地址
当spring security检查到session过期后,若未做任何配置,spring security会返回一个用户不友好的页面,因此我们通常需要设置一个地址,当spring security检查到session过期后,将请求重定位到我们的地址上,设置代码如下所示:
http.sessionManagement().expiredUrl("/login");
阻止认证而不是强制退出
当一个用户已经认证过了,在另外一个地方重新进行登录认证,spring security可以阻止其再次登录认证,从而保持原来的会话可用性;具体的代码设置如下所示:
http.sessionManager().maximumSession(1).maxSessionsPreventsLogin(true);
当时上述代码还存在一个问题,当用户登陆后,没有退出直接关闭浏览器,则再次打开浏览器时,此时浏览器的session若被删除的话,用户只能等到服务器的session过期后,才能再次登录。
一些常见问题
1.当采用传统的UserDetails认证登录时,若UserDetails的equals方法和hashcode方法没有进行有效的实现,可能会导致,同一个用户多次登录,但是不会触发登录退出事件,这是因为SessionRegistry是使用内存map来存储UserDetails的,对UserDetails的比较会调用其自身的equal方法;
2.当用户会话持久化到磁盘后,应用服务器重启时,会读取磁盘上的会话,这时已经使用有效会话登录的用户,应该是登录状态,但此时sessionRegistry的内存map是空,因此spring security会报告用户未登录,为解决该问题,有两种方法,一是自定义sessionRegistry的实现,并在容器中禁用会话持久化功能,二是必须实现容器特定的方式,以确保在启动时将持久化会话填充到内存映射中。
3.spring security较低的版本并没有在session的并发控制中实现‘记住我功能’;
4.session并发控制的缺省实现并不能用于集群环境中,因此其缺省实现是将session记录在内存map中,服务器1中记录的信息和服务器2记录的信息并不相同;
典型的业务场景
现在我们有这样一个业务场景,我允许用户多次登录,同时登录的用户可以查看相同的账号在不同的地方的登录session,并且对这些session进行管理,比如查看,删除等操作;如何使用spring security实现上述功能?
It is easy!
http.sessionManagement().maximumSession(-1).sessionRegistry(sessionRegistryImp()).expiredUrl("login")
@Bean
public SessionRegistry sessionRegistryImp(){
return new SessionRegistryImp();
}
接下来,我们就可以将SessionRegistry实例注入到Controller里面,通过SessionRegistry获取与当前认证用户相关的所有session,示例代码如下所示:
@GetMapping("/user/sessions/")
public String sessions(Authentication authentication,Model model){
List<SessionInformation> list=sessionRegistry.getAllSessions(authentication.getPrincipal(),false);
}
认证信息和HttpSession的关系
1.在每一个request的开始,SecurityContextPersistenceFilter负责通过SecurityContextRepository获取SecurityContext的实现;并将其设置到SecurityContextHolder中,随后可以在controller中通过SecurityContextHolder访问SecurityContext;
2.在请求结束,SecurityContextPersistenceFilter会从SecurityContextHolder中取出SecurityContext并将其保存到SecurityContextRepository中
The question that now arises is how is this related to HttpSession? This is all tied together by the default SecurityContextRepository implementation, which uses HttpSession。
SecurityContextRepository的缺省实现HttpSessionSecurityContextRepository,使用HttpSession检索和保存SecurityContext的实现,spring security除了只提供了一个SecurityContextRepository的实现即为HttpSessionSecurityContextRepository,但是我们可以定义自己的SecurityContextRepository实现。
控制HttpSession的创建
我们可以通过http.create-session(params)方法来控制HttpSession的创建,params的参数值如下图所示:
参数对HttpSession的控制并不全面,它只控制了HttpSession创建的一个子集,为了跟踪HttpSession的创建,我们可以使用DebugFilter来调试spring security,它会输出session的创建信息,当然DebugFilter不仅能调试Session,还能调试其他的信息。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。