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 ...
随机推荐
- C# .NET 获取枚举值的自定义属性
一.定义一个类 using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...
- vs2017常用快捷键
项目相关的快捷键 Ctrl + Shift + B = 生成项目 Ctrl + Alt + L = 显示 Solution Explorer(解决方案资源管理器) Ctrl + Shift + A = ...
- VS2010的快捷键乱
vs2010的快捷键乱了,点击回车会出现属性窗口,点击退格键会相当于编辑里面的撤销功能 点击ctrl+s会出现sharepoint窗口,在网上找了一个解决方式(很难找),原问在这: http://q. ...
- 【VB.NET】利用纯真IP数据库查询IP地址及信息
几年前从某个博客抄来的,已经忘记原地址了,如果需要C#版的,可以在博客园搜到吧.我因为自己用,所以转换为了VBNET代码,而且也放置了很久,今天无意间翻出来,就分享给大家吧. 首先,先下载 纯真数据库 ...
- Python MySQL - 创建/查询/删除数据库
#coding=utf-8 import mysql.connector import importlib import sys #连接数据库的信息 mydb = mysql.connector.co ...
- 【disruptor】1、关于disruptor中的SequenceBarrier对象
首先这个类的uml结构在disruptor中是这样的,里面只有部分的属性对象和函数内容,具体有什么作用,用到了再说,用不到我也不会... 1.那么这个对象有什么用呢? 注意我们这个类中有哪些属性: 我 ...
- POJ 2665
#include<iostream> #include<stdio.h> using namespace std; int main() { //freopen("a ...
- Spring的JavaMail实现异步发送邮件
具体背景就不说了,可以网上搜索相关知识,或者直接看Sping MailSender的官坊网页.这里就直接实战了(Java实现异步发送电子邮件,包含中文无乱码). Maven: <dependen ...
- 【转】Windows 8 desktop app中dll搜索路径设置的诡异现象,Bug?
原文地址:http://blog.csdn.net/my_business/article/details/8850151 某个桌面程序在win 8上运行异常的问题困扰了我有近一周,今天终于找到了根本 ...
- Is it possible to display icons in a PopupMenu?
I really like the new PopupMenu we got in 3.0, but I just can't display any icons next to the menu i ...