本篇主要讲述以下几点:

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的更多相关文章

  1. Spring Security之Remember me详解

    Remember me功能就是勾选"记住我"后,一次登录,后面在有效期内免登录. 先看具体配置: pom文件: <dependency> <groupId> ...

  2. Spring Security认证配置(三)

    学习本章之前,可以先了解下上篇Spring Security认证配置(二) 本篇想要达到这样几个目的: 1.登录成功处理 2.登录失败处理 3.调用方自定义登录后处理类型 具体配置代码如下: spri ...

  3. Spring Security认证配置(一)

    学习本章之前,可以先了解下上篇 Spring Security基本配置. 本篇主要讲述Spring Security基于表单,自定义用户认证配置(上篇中的配置,本篇将不再阐述).一共分为三步: 1.处 ...

  4. Spring Security 源码解析(一)

    上篇 Spring Security基本配置已讲述了Spring Security最简单的配置,本篇将开始分析其基本原理 在上篇中可以看到,在访问 http://localhost:18081/use ...

  5. Spring Security 解析(二) —— 认证过程

    Spring Security 解析(二) -- 认证过程   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .S ...

  6. springBoot+springSecurity 数据库动态管理用户、角色、权限

    使用spring Security3的四种方法概述 那么在Spring Security3的使用中,有4种方法: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中,已经实现过, ...

  7. 【Spring】12、Spring Security 四种使用方式

    spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: ...

  8. springBoot+springSecurity 数据库动态管理用户、角色、权限(二)

    序: 本文使用springboot+mybatis+SpringSecurity 实现数据库动态的管理用户.角色.权限管理 本文细分角色和权限,并将用户.角色.权限和资源均采用数据库存储,并且自定义滤 ...

  9. 【Spring】关于Boot应用中集成Spring Security你必须了解的那些事

    Spring Security Spring Security是Spring社区的一个顶级项目,也是Spring Boot官方推荐使用的Security框架.除了常规的Authentication和A ...

随机推荐

  1. MVC图片验证

    1.创建一个验证类,里面有生成验证码的两个方法. namespace YTJWGL_Common { public class ValidatorCodeTools { #region 生成校验码图片 ...

  2. MVC 视图不使用模板页的两种方法

    直接对view页面的Layout值设置null @{ Layout = null;//"~/Views/Shared/_Layout.cshtml"; } 对_ViewStart. ...

  3. UWP Button添加圆角阴影(三)

    原文:UWP Button添加圆角阴影(三) Composition DropShadow是CompositionAPI中的东西,使用Storyboard设置某个属性,就是频繁的触发put_xxx() ...

  4. 背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制

    [源码下载] 背水一战 Windows 10 (57) - 控件(集合类): ListViewBase - 增量加载, 分步绘制 作者:webabcd 介绍背水一战 Windows 10 之 控件(集 ...

  5. day02 基本数据类型与运算符

    day02 1.基本数据类型 2.算术运算符 +,-,*,/,%,++,-- 3.赋值运算符 =,+=,-=,*=,/=,%= 4.关系运算符 +=,-=,*=,/=,%=  结果是boolean类型 ...

  6. centos7----pstree

    centos 默认没有pstree 安装 yum -y install psmisc

  7. iOS数据持久化--用户属性

    一.简介 NSUserDefaults类是一个单例类,每个程序只有一个 NSUserDefaults对象,可以用来存储用户的属性,比如自动登录时候的账号密码等小型的数据. 二.使用 1.NSUserD ...

  8. docker 下安装mssql-server-linux

    docker search mssql 查找mssql镜像 docker pull microsoft/mssql-server-linux 拉去mssql镜像 docker images 查看镜像 ...

  9. 将python打包为.exe文件

    第一步:在https://pypi.python.org/pypi/PyInstaller/2.1 下载pyinstaller. 第二步:解压缩,在该目录下命令行中执行python setup.py ...

  10. golang 并发顺序输出数字

    参考 package main import ( "fmt" "sync/atomic" "time" ) func main() { va ...