本篇主要讲述以下几点:

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. C# .NET 获取枚举值的自定义属性

    一.定义一个类 using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...

  2. vs2017常用快捷键

    项目相关的快捷键 Ctrl + Shift + B = 生成项目 Ctrl + Alt + L = 显示 Solution Explorer(解决方案资源管理器) Ctrl + Shift + A = ...

  3. VS2010的快捷键乱

    vs2010的快捷键乱了,点击回车会出现属性窗口,点击退格键会相当于编辑里面的撤销功能 点击ctrl+s会出现sharepoint窗口,在网上找了一个解决方式(很难找),原问在这: http://q. ...

  4. 【VB.NET】利用纯真IP数据库查询IP地址及信息

    几年前从某个博客抄来的,已经忘记原地址了,如果需要C#版的,可以在博客园搜到吧.我因为自己用,所以转换为了VBNET代码,而且也放置了很久,今天无意间翻出来,就分享给大家吧. 首先,先下载 纯真数据库 ...

  5. Python MySQL - 创建/查询/删除数据库

    #coding=utf-8 import mysql.connector import importlib import sys #连接数据库的信息 mydb = mysql.connector.co ...

  6. 【disruptor】1、关于disruptor中的SequenceBarrier对象

    首先这个类的uml结构在disruptor中是这样的,里面只有部分的属性对象和函数内容,具体有什么作用,用到了再说,用不到我也不会... 1.那么这个对象有什么用呢? 注意我们这个类中有哪些属性: 我 ...

  7. POJ 2665

    #include<iostream> #include<stdio.h> using namespace std; int main() { //freopen("a ...

  8. Spring的JavaMail实现异步发送邮件

    具体背景就不说了,可以网上搜索相关知识,或者直接看Sping MailSender的官坊网页.这里就直接实战了(Java实现异步发送电子邮件,包含中文无乱码). Maven: <dependen ...

  9. 【转】Windows 8 desktop app中dll搜索路径设置的诡异现象,Bug?

    原文地址:http://blog.csdn.net/my_business/article/details/8850151 某个桌面程序在win 8上运行异常的问题困扰了我有近一周,今天终于找到了根本 ...

  10. 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 ...