原理探讨
当我们在项目中引入 Spring Security
的相关依赖后,默认的就是表单登录形式;俗话说:“听人劝,吃饱饭”,既然 Spring Security
已经给我们安排的明明白白了,我们就从表单登录开始吧。
在开始之前,我们可以站在 Spring Security
的角度上思考:如果我自己来实现表单登录的功能,那么我需要做哪些工作呢?
就我个人而言,我可能会考虑以下几点:
-
配置用户信息,存储如账号、密码等;密码不能以明文传输,需要加密功能
-
执行校验
-
认证成功或者失败的处理方案
可以简单的制作成如下流程图:
上方属于我们自己设想的实现方案,属于"低配版"模式,下面我们来看看 Spring Security
是怎么做的。 Spring Security
的思路和我们大同小异,优点在于其提供了很好的封装,提高了框架本身的可扩展性。
Spring Security
的实现步骤如下:
-
UsernamePasswordAuthenticationFilter
拦截器拦截前端传递的表单登录请求,将登录信息(username、password
)封装成UsernamePasswordAuthenticationToken
,传递给AuthenticationManager
认证管理器 -
AuthenticationManager
认证管理器根据Token
的类型遍历获取对应的Provider
,也即是DaoAuthenticationProvider
,执行认证流程 -
DaoAuthenticationProvider
依靠PasswordEncoder
和UserDetailsService
对登录请求进行验证 -
验证通过,由
AuthenticationSuccessHandler
认证成功处理器进行处理 -
验证失败,由
AuthenticationFailureHandler
认证失败处理器进行处理
制作成流程图如示:
这时你可能会一脸懵逼:这咋和刚刚我们自己设想的完全不一样呀~ 又是Manager
又是Provider
的;莫慌,且听我慢慢道来。
上面出现了很多新的概念,我们目前不需要十分细致的了解它们是怎么发挥作用的,只需要大概知道它们有什么用的即可;具体的介绍会在下篇《认证(二):表单登录认证流程源码解析》娓娓道来。
-
UsernamePasswordAuthenticationFilter
表单登录拦截器,用以捕获前端传递的登录信息(username、password
),并将登录信息封装成某些Token
。 -
AuthenticationManager
认证管理器,可简单的理解为分配工作的领导。DaoAuthenticationProvider
DAO认证处理器,相当于被安排干活的童鞋;从名字DAO也可以简单的推测出:它与数据库中的用户信息密不可分。 -
PasswordEncoder
密码加密器,密码不能铭文传输,需要加密。UserDetailsService
用户信息Service层,这个也很好理解,前端传递的登录信息肯定是有对应的数据库实体存储。 -
AuthenticationSuccessHandler
认证成功处理器AuthenticationFailureHandler
认证失败处理器。
经过上述的原理探讨,我们大体上能弄懂了整个表单登录有哪几个模块需要处理;可简单的总结为3个模块:
-
登录前置处理: 用户信息的封装、密码加密器的设置
-
登录中处理: 登录的校验
-
登录后置处理: 登录失败、登录成功的处理方案
小试牛刀
俗话说:“光说不练假把式”,那么就让我们来实战一番吧。
登录前置处理
作为一个Java Web项目,第一步当然是引入相关依赖;直接引入Spring Boot封装好的starter即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Step-1 配置用户信息
Spring Security
提供了UserDetails
接口,用于获取用户的基本信息(账号密码、权限集合、是否锁定等等),我们只需要根据自身的业务场景,实现该接口即可。
Spring Security提供的UserDetails.class接口
public interface UserDetails extends Serializable {
/**
* 用户权限集,默认需要添加ROLE_作为前缀
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 获取用户密码
*/
String getPassword();
/**
* UserDetails的接口
* 获取用户名
*/
String getUsername();
/**
* 账户是否未过期 --true则为未过期
*/
boolean isAccountNonExpired();
/**
* 账户是否未被锁定
*/
boolean isAccountNonLocked();
/**
* 账户凭证是否未过期
*/
boolean isCredentialsNonExpired();
/**
* 账户是否可用
*/
boolean isEnabled();
}
自定义业务相关的用户信息类,业务定义的UserInfo.class
必须带有username
和password
相关的信息,用于做用户验证;项目根据自身需求来判断是否需要使用下