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 ...
随机推荐
- MGW PCI Framework Architecture
MGW执行SWBT/FT cases是主要是利用Ant项目. Ant的概念 可能有些读者并不连接什么是Ant以及入可使用它,但只要使用通过Linux系统得读者,应该知道make这个命令.当编译Linu ...
- C# 实现简单仿QQ登陆注册功能
闲来没事,想做一个仿QQ登陆注册的winform,于是利用工作之余,根据自己的掌握和查阅的资料,历时4天修改完成,新手水平,希望和大家共同学习进步,有不同见解希望提出! 废话不多说,进入正题: 先来看 ...
- [leetcode.com]算法题目 - Pascal's Triangle
Given numRows, generate the first numRows of Pascal's triangle. For example, given numRows = 5,Retur ...
- urllib2 的使用与介绍
爬虫简介 什么是爬虫? 爬虫:就是抓取网页数据的程序. HTTP和HTTPS HTTP协议(HyperText Transfer Protocol,超文本传输协议):是一种发布和接收 HTML页面的 ...
- flask加vue 动画 加载更多
曾经使用flask_paginate(地址:https://blog.csdn.net/qq_42239520/article/details/80378095)进行分页,现在又想新的想法,怎么才能和 ...
- 03-02 Java键盘录入
键盘录入基本格式: /* 为了让程序的数据更符合开发的数据,我们就加入了键盘录入. 让程序更灵活一下. 那么,我们如何实现键盘数据的录入呢? A:导包 格式: import java.util.Sca ...
- MVC3学习:利用mvc3+ajax结合MVCPager实现分页
本例使用表格Users(Uid,UserName,PassWord),数据库访问使用EF first code. public class Users { [Key] public int Uid { ...
- CentOS6.7-64bit编译hadoop2.6.4
1.下载maven(apache-maven-3.3.3-bin.tar.gz) http://archive.apache.org/dist/maven/maven-3/3.3.3/binaries ...
- Android入门学习总结
1.Manifest.xml是程序运行时读取的文件,是核心的配置文件:也是从中读取Activity 2.主要的代码文件存放在MainActivity.java,里面固定会有onCreate函数会通过s ...
- SSM事务——事务回滚如何拿到返回值
MySQL数据库一共向用户提供了包括BDB.HEAP.ISAM.MERGE.MyISAM.InnoDB以及Gemeni这7种Mysql表类型.其中BDB.InnoDB属于事务安全类表,而其他属于事务非 ...