集群环境下SpringSecurity处理Session
写在前面通过前面《内存保存用户+自动踢掉登录用户》、《传统方式+JPA+自动踢掉登录用户》、《前后端分离+JPA+自动踢掉登录用户》三篇的学习,我们已经对SpringSecurity中如何自动踢掉已登录用户有了较为清晰的认识,不过前面学习的都是基于单体应用,当项目是集群化部署时,上述配置还能使用么?如果不能使用,那么应该采用什么方式呢?带着这些问题我们来进入本篇的学习。
注意本篇使用的操作系统为Windows,所涉及到的Redis和Nginx均是在Windows上面部署的。
集群会话方案在传统的单体服务架构中,通常只有一台服务器,因此就不存在Session共享问题。但是在分布式或者集群项目中,Session共享是一个必须面对的问题。
下面是一个简单的集群项目架构图:
从图中可以知道,当客户端发起一个请求,此时请求到达Nginx上,被Nginx转发到TomcatA上,之后TomcatA往Session中保存了一份数据,之后客户端又发起一次请求,但是这个请求被Nginx转发到TomcatB上,由于TomcatB中不存在之前的Session信息,因此无法从中获取数据。
这个现象在分布式或者 ...
防御固定会话攻击
写在前面在前面《自动踢掉登录用户》一文中,我们通过使用SpringSecurity中的Session并发控制实现了像QQ一样的功能,即当用户在一台设备上登录成功,之后会自动踢掉另一台设备上的已登录。
其实SpringSecurity中的Session功能非常强大,本篇要学的就是是什么是会话固定攻击以及SpringSecurity中如何防御会话固定攻击,此时就需要使用到Session。
项目实例化第一步,使用IDEA创建一个名为fixation-attack的SpringBoot工程,并在其pom.xml依赖文件中添加如下依赖:
123456789101112131415161718192021<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ...
自带防火墙学习
写在前面前面我们对SpringSecurity中用户登录相关内容进行了深度学习,接下来开始学习SpringSecurity自带的防火墙,了解和使用防火墙对于提升系统的安全性有重要帮助。
杂谈在学习SpringSecurity之前,我们对Shiro框架进行了学习,发现它非常轻量,没有这么复杂的功能和配置。同时随着对SpringSecurity框架的深度学习,我们发现它底层其实用的还是Servlet的那套东西?就前面所述的自动踢掉登录用户这一功能来说,开发者完全可以自定义一个Filter来实现请求拦截,而且逻辑和配置非常简单。
尽管是可以这么操作,但是笔者不建议大家这样操作,因为我们自定义Filter可能仅仅是为了让认证和授权等功能变得简单,但是却忽略了安全等问题,显然安全必须是首要考虑的。
其实各种各样的Web攻击每天都在发生,可能你感知不到,那是因为有系统在保护着,小到系统本身自带的攻击防御,大到公司的防火墙。如果你自定义了Filter,那么你就需要针对不同的攻击,书写对应的代码来防护,毫无疑问,这就要求开发者不仅对一些常见的Web攻击,如固定会话攻击,CSRF攻击等有较为详细的了解, ...
前后端分离+JPA+自动踢掉登录用户
写在前面在前一篇《自动踢掉登录用户》一文中,我们采用的是前后端不分离模式,但是在前后端分离盛行的当下,有必要对此模式下的自动踢掉用户进行学习。同时在前一文中,用户信息都是配置在内存中,而实际工作中都是将其放入数据库中,因此需要切换数据源为数据库。需要注意的是,在使用SpringSecurity中的Session做并发处理时,直接将内存中的用户切换为数据库中的用户,也就是将内存源切换为数据库源是会出现问题的,接下来就来细说这个问题。
前后端分离项目实例化考虑到此处主要学习如何在前后端分离模式下,实现自动踢掉登录用户,因此就只是单纯的将用户存储在数据库中,而不进行任何的权限控制。同时此处登录使用JSON格式。
第一步,使用IDEA创建一个名为kickoffuser-json的SpringBoot工程,并在其pom.xml依赖文件中添加如下依赖:
12345678910111213141516171819202122232425262728293031323334<dependencies> <dependency> <groupId> ...
传统方式+JPA+自动踢掉登录用户
写在前面在《自动踢掉登录用户》一文中,出于简单考率,我们将用户信息保存在内存中,但是在实际工作中都是将用户信息保存早数据库中。看到这里,小伙伴是不是觉得只需将数据库保存用户替换为内存保存用户,完全没必要新开一篇文章,是的通常都是可以直接这么操作,但是这里仅仅这么操作是不行的,因此此处有必要单独进行介绍。
项目初始化考虑到此处主要学习如何在前后端不分离模式下,实现自动踢掉登录用户,因此就只是单纯的将用户存储在数据库中,而不进行任何的权限控制。
第一步,使用IDEA创建一个名为kickoffuser-jpa的SpringBoot工程,并在其pom.xml依赖文件中添加如下依赖:
12345678910111213141516171819202122232425262728293031323334<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter ...
内存保存用户+自动踢掉登录用户
写在前面经过前面的学习,我们已经对SpringSecurity用户登录内容有了较为深刻的认识,接下来学习一个较为有意思的功能—自动踢掉登录用户。
你可能不知道什么是“自动踢掉登录用户”,但是你可能遇到过这种场景:当你在A电脑上登录了QQ,然后再在B电脑上登录时,QQ就会将你从A电脑上踢下线,也就是告知你同一时刻同一平台你只能登录一个实例。
从本篇文章开始,如果没有特殊说明,那么都是新建一个gitee分支,在新的项目上进行编码。在gitee上新建一个kickoff-user分支,然后将本地分支切换过去,接下来开始进行代码逻辑编写。
需求描述在实际工作中,出于安全考量,我们可能要求某个系统只允许一个用户在一个终端上登录,更有甚者要求一个用户在一个设备上登录。如钉钉,这款软件就规定用户最多只能在三台手机上登录(仅仅针对手机端),显然这就是对用户登录的设备进行了绑定。
需要说明的是,终端和设备两者是不同的,终端包括PC、手机端等方式,而设备就是指单纯的某台具体的手机。
要实现上述功能,即一个用户无法同时在两台设备上登录,可以有两种实现思路:(1)后来的登录用户踢掉前面已经登录的用户,QQ就是这 ...
获取登录额外信息
写在前面通过前面《详解登录流程》一文的学习,我们已经对用户登录流程、认证过程和保存用户信息等内容有了一个较为清晰的认识。同时我们也对用户登录流程和认证过程分别用了更为详细的内容去进行学习,那么本篇就花点时间来学习关于保存用户信息等那些事。
Authentication对象首先阅读《详解登录流程》一文,之后再来阅读本部分内容会容易很多。
前面我们曾多次提到过Authentication这个接口,它用来保存用户的登录信息,这里再次贴上该接口的源码:
12345678910111213public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAu ...
自定义认证逻辑
写在前面在前面我们对SpringSecurity中的登录流程进行了较为详细的分析,但是采用的都是系统默认的认证逻辑,这种方式在学习中尚能使用,但是在实际工作中一般都会自定义登录逻辑。笔者结合自己实际工作中的一些应用来介绍一种比较常用的自定义认证逻辑。
知识回顾前面我们在《添加登录验证码》和《前后端分离JSON格式登录实现》两篇文章中,通过自定义过滤器,并在过滤器中实现了相应的逻辑,这些也是自定义认证逻辑的范畴,只不过是最为基础罢了,但是它们都存在一些问题,如下面的例子所述的那样。
假设现在有一个系统,我们给它添加了一个验证码,同时为了校验验证码,需要自定义一个过滤器,并将该过滤器放入SpringSecurity的过滤器链中,之后每次请求都会通过该过滤器。这样的逻辑看似没有问题,但是你仔细想就会发现,我们仅仅需要登录的请求经过该过滤器,其他请求是无需经过的,因此如果你对性能有较为严苛的要求,那么就有必要对上述逻辑进行修改。
认证流程分析首先阅读《详解登录流程》一文,之后再来阅读本部分内容会容易很多。
通过查阅ProviderManager#authenticate()方法中的源码可以知道 ...
令牌持久化和二次验证
写在前面前一篇学习了如何实现自动登录,但是随之而来的是以牺牲系统安全为代价,这一点在很多场景下都是不可取的,因此就必须对自动登录的核心—令牌进行一些安全提升,或者采用二次验证等方式来提升系统的安全性。
令牌持久化概念前面提到过一个makeTokenSignature()方法,该方法的逻辑是计算令牌过期时间、用户名、密码和盐Key参数所构成字符串的哈希值:
12345678910protected String makeTokenSignature(long tokenExpiryTime, String username, String password) { String data = username + ":" + tokenExpiryTime + ":" + password + ":" + this.getKey(); try { MessageDigest digest = MessageDigest.getInstance("MD5 ...
实现自动登录
写在前面在前面对登录流程进行了较为细致的学习之后,接下来实现一个常用的功能—自动登录。请注意本篇新建了一个工程auto-login,不再使用之前的代码。
使用场景以常用的QQ邮箱为例,如下所示界面就是支持自动登录:
不仅仅是这里举例的QQ邮箱,很多网站都有这个功能。对于用户来说,每次登录都需要输出用户名和密码不仅增加了登录难度,降低用户体验,更重要的是账号被盗的风险也随之提升。
自动登录,说白了就是用户在登录成功后,那么在接下来的一段时间里,就算发生了诸如用户关闭浏览器、服务器宕机重启等行为时,此时用户依旧可以保持之前的登录状态,而不用重新登录。SpringSecurity对于自动登录提供了简易的配置方式,开发者通过简单的一些配置就能实现较为复杂的自动登录功能。
工程初始化第一步,使用IDEA创建一个名为auto-login的SpringBoot工程,之后选择添加Web和SpringSecurity依赖。
第二步,在application.yml配置文件中新增如下配置信息,用于自定义登录用户名和密码:
12345spring: security: user: nam ...