AuthenticationManager、ProviderManager
本篇主要讲述以下几点:
1、AuthenticationManager、ProviderManager和AuthenticationProvider三者之间的关系
2、以UsernamePasswordAuthenticationFilter为例,如何使用AuthenticationProvider的子类AbstractUserDetailsAuthenticationProvider、
DaoAuthenticationProvider来验证用户名密码
3、Authentication、UserDetails的内部结构
先来看一张时序图:
从上图可以看出验证逻辑为:
1、在UsernamePasswordAuthenticationFilter的attemptAuthentication()方法中,调用AuthenticationManager进行认证
2、AuthenticationManager接收Authentication对象作为参数,并通过authenticate方法对其进行验证(实际由其实现类ProviderManager完成)
3、在ProviderManager的authenticate方法中,轮训成员变量List<AuthenticationProvider> providers。该providers中如果有一个
AuthenticationProvider的supports函数返回true,那么就会调用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个
认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。
4、UsernamePasswordAuthenticationToken实现了Authentication,主要是将用户输入的用户名密码进行封装,并提供给
AuthenticationManager进行验证,验证成功后,返回一个认证成功的UsernamePasswordAuthenticationToken对象
AuthenticationManager
AuthenticationManager是一个接口,是认证方法的入口,接收一个Authentication对象作为参数
public interface AuthenticationManager { Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
ProviderManager
它是AuthenticationManager的一个实现类,实现了authenticate(Authentication authentication)方法,还有一个成员变量
List<AuthenticationProvider> providers
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean { ...... private List<AuthenticationProvider> providers = Collections.emptyList(); public Authentication authenticate(Authentication authentication)
throws AuthenticationException { ...... } }
AuthenticationProvider
AuthenticationProvider也是一个接口,包含两个函数authenticate和supports。当Spring Security默认提供的Provider不能满足需求的时候,可以通过实现AuthenticationProvider接口来扩展出不同的认证提供者
public interface AuthenticationProvider { //通过参数Authentication对象,进行认证
Authentication authenticate(Authentication authentication)
throws AuthenticationException; //是否支持该认证类型
boolean supports(Class<?> authentication); }
Authentication
Authentication是一个接口,通过该接口可以获得用户相关信息、安全实体的标识以及认证请求的上下文信息等
在Spring Security中,有很多Authentication的实现类。如UsernamePasswordAuthenticationToken、AnonymousAuthenticationToken和
RememberMeAuthenticationToken等等
通常不会被扩展,除非是为了支持某种特定类型的认证
public interface Authentication extends Principal, Serializable { //权限结合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_ADMIN")返回字符串权限集合
Collection<? extends GrantedAuthority> getAuthorities(); //用户名密码认证时可以理解为密码
Object getCredentials(); //认证时包含的一些信息。如remoteAddress、sessionId
Object getDetails(); //用户名密码认证时可理解时用户名
Object getPrincipal(); //是否被认证,认证为true
boolean isAuthenticated(); //设置是否被认证
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
UserDetails
UserDetails也是一个接口,主要封装用户名密码是否过期、是否可用等信息
public interface UserDetails extends Serializable {
//权限集合
Collection<? extends GrantedAuthority> getAuthorities(); //密码
String getPassword(); //用户名
String getUsername(); //用户名是否没有过期
boolean isAccountNonExpired(); //用户名是否没有锁定
boolean isAccountNonLocked(); //用户密码是否没有过期
boolean isCredentialsNonExpired(); //账号是否可用(可理解为是否删除)
boolean isEnabled();
}
接下来看具体的实现方法:
ProviderManager
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//获取当前的Authentication的认证类型
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
//遍历所有的providers
for (AuthenticationProvider provider : getProviders()) {
//判断该provider是否支持当前的认证类型。不支持,遍历下一个
if (!provider.supports(toTest)) {
continue;
} if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
} try {
//调用provider的authenticat方法认证
result = provider.authenticate(authentication); if (result != null) {
//认证通过的话,将认证结果的details赋值到当前认证对象authentication。然后跳出循环
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
} ......
}
AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider是 AuthenticationProvider 的核心实现类
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//如果authentication不是UsernamePasswordAuthenticationToken类型,则抛出异常
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported")); // 获取用户名
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName(); //从缓存中获取UserDetails
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username); //缓存中没有,则从子类DaoAuthenticationProvider中获取
if (user == null) {
cacheWasUsed = false; try {
//获取用户信息。由子类DaoAuthenticationProvider实现
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
} ...... } try {
//前检查。由DefaultPreAuthenticationChecks实现(主要判断当前用户是否锁定,过期,冻结User)
preAuthenticationChecks.check(user);
//附加检查。由子类DaoAuthenticationProvider实现
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
......
} //后检查。由DefaultPostAuthenticationChecks实现(检测密码是否过期)
postAuthenticationChecks.check(user); if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
} Object principalToReturn = user; if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
} //将已通过验证的用户信息封装成 UsernamePasswordAuthenticationToken 对象并返回
return createSuccessAuthentication(principalToReturn, authentication, user);
}
1、前检查和后检查的参数为UserDetails,正好对应UserDetails中的4个isXXX方法
2、retrieveUser()和additionalAuthenticationChecks()由子类DaoAuthenticationProvider实现
3、createSuccessAuthentication如下:
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
//重新封装成UsernamePasswordAuthenticationToken。包含用户名、密码,以及对应的权限
//该构造方法会给父类Authentication赋值: super.setAuthenticated(true)
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails()); return result;
}
DaoAuthenticationProvider
DaoAuthenticationProvider实现了父类的retrieveUser()和additionalAuthenticationChecks()方法
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser; try {
//调用UserDetailsService接口的loadUserByUsername获取用户信息
//通过实现UserDetailsService接口来扩展对用户密码的校验
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
} ...... //如果找不到该用户,则抛出异常
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
Object salt = null; if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
} //密码为空,则直接抛出异常
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
} //获取用户输入的密码
String presentedPassword = authentication.getCredentials().toString(); //将缓存中的密码(也可能是自定义查询的密码)与用户输入密码匹配
//如果匹配不上,则抛出异常
if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
presentedPassword, salt)) {
logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
关于UserDetailsService.loadUserByUsername方法,可参考Spring Security认证配置(一)
AuthenticationManager、ProviderManager的更多相关文章
- Spring Security之Remember me详解
Remember me功能就是勾选"记住我"后,一次登录,后面在有效期内免登录. 先看具体配置: pom文件: <dependency> <groupId> ...
- Spring Security认证配置(三)
学习本章之前,可以先了解下上篇Spring Security认证配置(二) 本篇想要达到这样几个目的: 1.登录成功处理 2.登录失败处理 3.调用方自定义登录后处理类型 具体配置代码如下: spri ...
- Spring Security认证配置(一)
学习本章之前,可以先了解下上篇 Spring Security基本配置. 本篇主要讲述Spring Security基于表单,自定义用户认证配置(上篇中的配置,本篇将不再阐述).一共分为三步: 1.处 ...
- Spring Security 源码解析(一)
上篇 Spring Security基本配置已讲述了Spring Security最简单的配置,本篇将开始分析其基本原理 在上篇中可以看到,在访问 http://localhost:18081/use ...
- Spring Security 解析(二) —— 认证过程
Spring Security 解析(二) -- 认证过程 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .S ...
- springBoot+springSecurity 数据库动态管理用户、角色、权限
使用spring Security3的四种方法概述 那么在Spring Security3的使用中,有4种方法: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中,已经实现过, ...
- 【Spring】12、Spring Security 四种使用方式
spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: ...
- springBoot+springSecurity 数据库动态管理用户、角色、权限(二)
序: 本文使用springboot+mybatis+SpringSecurity 实现数据库动态的管理用户.角色.权限管理 本文细分角色和权限,并将用户.角色.权限和资源均采用数据库存储,并且自定义滤 ...
- 【Spring】关于Boot应用中集成Spring Security你必须了解的那些事
Spring Security Spring Security是Spring社区的一个顶级项目,也是Spring Boot官方推荐使用的Security框架.除了常规的Authentication和A ...
随机推荐
- MVC图片验证
1.创建一个验证类,里面有生成验证码的两个方法. namespace YTJWGL_Common { public class ValidatorCodeTools { #region 生成校验码图片 ...
- MVC 视图不使用模板页的两种方法
直接对view页面的Layout值设置null @{ Layout = null;//"~/Views/Shared/_Layout.cshtml"; } 对_ViewStart. ...
- UWP Button添加圆角阴影(三)
原文:UWP Button添加圆角阴影(三) Composition DropShadow是CompositionAPI中的东西,使用Storyboard设置某个属性,就是频繁的触发put_xxx() ...
- 背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制
[源码下载] 背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制 作者:webabcd 介绍背水一战 Windows 10 之 控件(集 ...
- day02 基本数据类型与运算符
day02 1.基本数据类型 2.算术运算符 +,-,*,/,%,++,-- 3.赋值运算符 =,+=,-=,*=,/=,%= 4.关系运算符 +=,-=,*=,/=,%= 结果是boolean类型 ...
- centos7----pstree
centos 默认没有pstree 安装 yum -y install psmisc
- iOS数据持久化--用户属性
一.简介 NSUserDefaults类是一个单例类,每个程序只有一个 NSUserDefaults对象,可以用来存储用户的属性,比如自动登录时候的账号密码等小型的数据. 二.使用 1.NSUserD ...
- docker 下安装mssql-server-linux
docker search mssql 查找mssql镜像 docker pull microsoft/mssql-server-linux 拉去mssql镜像 docker images 查看镜像 ...
- 将python打包为.exe文件
第一步:在https://pypi.python.org/pypi/PyInstaller/2.1 下载pyinstaller. 第二步:解压缩,在该目录下命令行中执行python setup.py ...
- golang 并发顺序输出数字
参考 package main import ( "fmt" "sync/atomic" "time" ) func main() { va ...