内存保存用户+自动踢掉登录用户
写在前面经过前面的学习,我们已经对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 ...
填坑,前后端分离JSON格式登录实现
写在前面如果你之前阅读过笔者写过的《前后端分离回调和注销登录》和《添加登录验证码》两篇文章,会发现一个比较尴尬的事情,当时我们说好是采用前后端分离模式,前后端之间以JOSN格式的数据进行传输,但是在用户登录的时候,我们其实并没有采用JSON格式,依旧使用了Key/Value键值对形式。除此之外,其余所有的POST请求都采用了JSON格式,这是当时笔者偷了一个懒所导致的。原因在于SpringSecurity中默认的登录数据格式就是Key/Value键值对形式,因此我就直接使用了,没做修改,但是在实际工作中有必要进行全部统一,因此本篇就来将其进行改造,使之也采用JSON格式来传输数据。
现有方式通过《详解登录流程》一文,我们知道用户登录信息是在UsernamePasswordAuthenticationFilter类中处理的,里面有三个重要的方法:
123456789101112131415161718192021222324public Authentication attemptAuthentication(HttpServletRequest request, HttpServlet ...
添加登录验证码
写在前面在前一篇《详解登录流程》一文中,笔者提到当开发者需要在SpringSecurity中自定义一个登录验证码或者将登录参数修改为JSON时,都需要自定义自己的Filter类,并继承这个AbstractAuthenticationProcessingFilter类,那么接下来的两篇就分别介绍如何自定义登录验证码和将登录参数修改为JSON格式。
本文是在之前的security-jpa项目上进行修改的。
生成验证码既然想在SpringSecurity中使用验证码,那么首先是生成验证码,这里采用Java来生成验证码。
新建一个utils包,并在里面新建一个生成验证码的工具类VerifyCode,里面的代码如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 ...
详解登录流程
写在前面在前面学习前后端分离模式下的回调和注销时,提到了密码擦除,当时没有对SpringSecurity的登录流程进行梳理,那么本篇就来详细学习登录流程。
场景描述现在有一个场景,用户在服务端安全管理选择了SpringSecurity,那么用户登录成功后,SpringSecurity会将用户信息保存在Session中,但是具体保存的位置,就目前而言开发者是不知道的,但是现在就是想知道这个信息的保存位置,以便当用户在前端修改了自己的信息,在不重新登录的情况下,开发者如何获取到用户的最新信息?这个场景在实际工作中是很常见的。
Authentication对象如果你之前使用过Shiro框架,那么就知道在Shiro框架中与用户认证相关的信息都在AuthenticationToken接口中,查看一下这个接口的源码:
12345public interface AuthenticationToken extends Serializable { Object getPrincipal(); Object getCredentials();}
它只有两个方法,一个获取 ...
Spring Data JPA操作数据库
写在前面在前一篇我们学习了如何基于数据库来实现授权操作,使用了JdbcUserDetailsManager类,同时发现它底层使用的是JdbcTemplate,用户操作非常不方便,且使用了JdbcUserDetailsManager自带的数据库,这些表和字段是无法满足实际的开发需要,因此通常做法是开发者自定义授权数据库和表。
出于操作简单的考虑,这里使用Spring Data JPA来代替JdbcTemplate进而完成对数据库的操作。
创建工程使用IDEA创建一个名为security-jpa的SpringBoot工程:
当然也可以在创建项目的时候不添加任何依赖,而是在后续pom.xml依赖文件中添加如下依赖:
12345678910111213141516171819202122232425262728293031323334<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...
基于数据库的授权操作
写在前面前面学习的都是基于内存的授权操作,但是在实际开发过程中都是将数据存储在数据库中,因此本篇就来学习如何基于数据库来实现授权操作。
UserDetailService接口通过前面的学习,我们知道SpringSecurity存在多种认证方式,查看一下这个AuthenticationManagerBuilder类,可以发现它存在inMemoryAuthentication(内存)、jdbcAuthentication(数据库)和ldapAuthentication(LDAP)等三种认证方式:
1234567891011public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception { return (InMemoryUserDetailsManagerConfigurer)this.apply(new InMemoryUserDetailsManagerConfigurer());} ...
