AuthenticationManager相关类图

AuthenticationManager验证过程

AuthenticationManager验证过程涉及到的类和接口较多,我们就从这里开始逐一分析,首先我手画了一张图作为索引,这张图说明了各个类和接口之间的关系。

AuthenticationManager 为认证管理接口类,其定义了认证方法authenticate()。

ProviderManager 为验证管理类,实现了接口AuthenticationManager ,并在认证方法authenticate() 中将身份认证委托给具有认证资格的AuthenticationProvider 进行身份认证。

从上图中我们可以看到AuthenticationManager的实现类有很多,至于为什么我只提及到ProviderManager,有时间的小伙伴可以进行源码跟踪就能发现。

ProviderManager的成员变量

关于AuthenticationEventPublisher不懂的小伙伴可以查看Security中的认证事件发布器 。

providers存储了一个 AuthenticationProvider 类型的list。和Security中的配置文件相对应。

MessageSourceAccessor一个国际化消息来源访问器,Security中用于信息提示。

AuthenticationProvider

接口认证类,定义了认证方法authenticate() 。

AbstractUserDetailsAuthenticationProvider 为认证抽象类,实现了接口 AuthenticationProvider 定义的认证方法 authenticate()。还定义了抽象方法 retrieveUser() 用于查询数据库用户信息,以及抽象方法 additionalAuthenticationChecks() 用作额外的身份验证检查。

DaoAuthenticationProvider

继承自抽象类AbstractUserDetailsAuthenticationProvider,实现了该类的方法 retrieveUser() 和 additionalAuthenticationChecks()。

DaoAuthenticationProvider 中还具有四个成员变量,分别是

USER_NOT_FOUND_PASSWORD

顾名思义,该变量是与PasswordEncoder一同使用的,当Security未找到用户时,用于PasswordEncoder.matches()执行的明文密码,以防止恶意用户确定用户名是否有效的旁路攻击的可能性。

PasswordEncoder

密码编码器,Security中的主要作用是用于将明文密码转换成密文,它采用SHA-256算法,迭代1024次,使用一个密钥(site-wide secret)以及8位随机盐对原密码进行加密。

userNotFoundEncodedPassword

同上方USER_NOT_FOUND_PASSWORD一致,只不过Security将其修饰为volatile的,确保了该变量不会因为编译器的优化而被省略。

关于volatile关键字不懂的请查看volatile理解

UserDetailsService

这个变量相信很多人都知道,不做过多的解释,Security中用于查询用户详细信息的接口

UserDetailsPasswordService

顾名思义,该接口用于修改用户的密码,只有在使用持久存储库时才有效,基于内存的方式会抛异常。(用处不大,只做了解即可)

流程分析

static final class AuthenticationManagerDelegator implements AuthenticationManager {

private AuthenticationManagerBuilder delegateBuilder;

private AuthenticationManager delegate;

private final Object delegateMonitor = new Object();

AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder) {

Assert.notNull(delegateBuilder, "delegateBuilder cannot be null");

this.delegateBuilder = delegateBuilder;

}

@Override

public Authentication authenticate(Authentication authentication)

throws AuthenticationException {

if (this.delegate != null) {

return this.delegate.authenticate(authentication);

}

synchronized (this.delegateMonitor) {

if (this.delegate == null) {

this.delegate = this.delegateBuilder.getObject();

this.delegateBuilder = null;

}

}

return this.delegate.authenticate(authentication);

}

@Override

public String toString() {

return "AuthenticationManagerDelegator [delegate=" + this.delegate + "]";

}

}

1、Security认证的入口为AuthenticationManager的authenticate()方法,从上面代码中我们可以看出,AuthenticationManagerDelegator使用了单例模式来防止AuthenticationManager在初始化时发生无限递归,因此我们只分析上方的两个实现类OAuth2AuthenticationManager和ProviderManager。

OAuth2AuthenticationManager

OAuth2AuthenticationManager的authenticate()的方法代码如下:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

if (authentication == null) {

throw new InvalidTokenException("Invalid token (token not found)");

}

(1) String token = (String) authentication.getPrincipal();

(2) OAuth2Authentication auth = tokenServices.loadAuthentication(token);

if (auth == null) {

throw new InvalidTokenException("Invalid token: " + token);

}

Collection resourceIds = auth.getOAuth2Request().getResourceIds();

if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {

throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");

}

(3) checkClientDetails(auth);

if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {

OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();

// Guard against a cached copy of the same details

if (!details.equals(auth.getDetails())) {

// Preserve the authentication details from the one loaded by token services

details.setDecodedDetails(auth.getDetails());

}

}

auth.setDetails(authentication.getDetails());

auth.setAuthenticated(true);

return auth;

}

private void checkClientDetails(OAuth2Authentication auth) {

if (clientDetailsService != null) {

ClientDetails client;

try {

client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId());

}

catch (ClientRegistrationException e) {

throw new OAuth2AccessDeniedException("Invalid token contains invalid client id");

}

Set allowed = client.getScope();

for (String scope : auth.getOAuth2Request().getScope()) {

if (!allowed.contains(scope)) {

throw new OAuth2AccessDeniedException(

"Invalid token contains disallowed scope (" + scope + ") for this client");

}

}

}

}

OAuth2AuthenticationManager用于集成了OAuth2.0时使用的,如果没有用到,可以忽略。

其中:

(1)处的代码,期望传入的身份验证请求具有一个主体值,该主体值是一个访问令牌值(一般在(authorization header)请求头中)

(2)处从ResourceServerTokenServices通过查询数据库中 oauth_client_details该表,加载身份验证。

通过(3)处检查资源id是否包含在授权请求中。检查通过之后封装OAuth2认证实体返回给UsernamePasswordAuthenticationFilter以确定认证成功或失败。

ProviderManager

ProviderManager的authenticate()的方法代码如下:

public Authentication authenticate(Authentication authentication)

throws AuthenticationException {

Class extends Authentication> toTest = authentication.getClass();

AuthenticationException lastException = null;

AuthenticationException parentException = null;

Authentication result = null;

Authentication parentResult = null;

boolean debug = logger.isDebugEnabled();

(1) for (AuthenticationProvider provider : getProviders()) {

if (!provider.supports(toTest)) {

continue;

}

if (debug) {

logger.debug("Authentication attempt using "

+ provider.getClass().getName());

}

try {

(2) result = provider.authenticate(authentication);

if (result != null) {

copyDetails(authentication, result);

break;

}

}

catch (AccountStatusException | InternalAuthenticationServiceException e) {

prepareException(e, authentication);

// SEC-546: Avoid polling additional providers if auth failure is due to

// invalid account status

throw e;

} catch (AuthenticationException e) {

lastException = e;

}

}

if (result == null && parent != null) {

// Allow the parent to try.

try {

result = parentResult = parent.authenticate(authentication);

}

catch (ProviderNotFoundException e) {

// ignore as we will throw below if no other exception occurred prior to

// calling parent and the parent

// may throw ProviderNotFound even though a provider in the child already

// handled the request

}

catch (AuthenticationException e) {

lastException = parentException = e;

}

}

if (result != null) {

if (eraseCredentialsAfterAuthentication

&& (result instanceof CredentialsContainer)) {

// Authentication is complete. Remove credentials and other secret data

// from authentication

((CredentialsContainer) result).eraseCredentials();

}

// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent

// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it

if (parentResult == null) {

eventPublisher.publishAuthenticationSuccess(result);

}

return result;

}

// Parent was null, or didn't authenticate (or throw an exception).

if (lastException == null) {

lastException = new ProviderNotFoundException(messages.getMessage(

"ProviderManager.providerNotFound",

new Object[] { toTest.getName() },

"No AuthenticationProvider found for {0}"));

}

// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent

// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it

if (parentException == null) {

prepareException(lastException, authentication);

}

throw lastException;

}

其中:

(1)处的代码从ProviderManager的属性providers[List]中通过for循环拿到支持该类认证的AuthenticationProvider用于认证处理。

(2)处的代码,对用户进行身份认证,认证过程如下所示。

在上面(2)处的代码,使用了AbstractUserDetailsAuthenticationProvider的authenticate()方法,接下来具体分析该方法,代码如下:

public Authentication authenticate(Authentication authentication)

throws AuthenticationException {

Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,

() -> messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.onlySupports",

"Only UsernamePasswordAuthenticationToken is supported"));

// Determine username

String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"

: authentication.getName();

boolean cacheWasUsed = true;

UserDetails user = this.userCache.getUserFromCache(username);

if (user == null) {

cacheWasUsed = false;

try {

(1) user = retrieveUser(username,

(UsernamePasswordAuthenticationToken) authentication);

}

catch (UsernameNotFoundException notFound) {

logger.debug("User '" + username + "' not found");

if (hideUserNotFoundExceptions) {

throw new BadCredentialsException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.badCredentials",

"Bad credentials"));

}

else {

throw notFound;

}

}

Assert.notNull(user,

"retrieveUser returned null - a violation of the interface contract");

}

try {

preAuthenticationChecks.check(user);

(2) additionalAuthenticationChecks(user,

(UsernamePasswordAuthenticationToken) authentication);

}

catch (AuthenticationException exception) {

if (cacheWasUsed) {

// There was a problem, so try again after checking

// we're using latest data (i.e. not from the cache)

cacheWasUsed = false;

user = retrieveUser(username,

(UsernamePasswordAuthenticationToken) authentication);

preAuthenticationChecks.check(user);

additionalAuthenticationChecks(user,

(UsernamePasswordAuthenticationToken) authentication);

}

else {

throw exception;

}

}

postAuthenticationChecks.check(user);

if (!cacheWasUsed) {

this.userCache.putUserInCache(user);

}

Object principalToReturn = user;

if (forcePrincipalAsString) {

principalToReturn = user.getUsername();

}

(3) return createSuccessAuthentication(principalToReturn, authentication, user);

}

(3)处创建一个成功的身份认证令牌并将用户认证信息其放置到UsernamePasswordAuthenticationToken中。

查看源码我们得知,AbstractUserDetailsAuthenticationProvider的(1)处和(2)处调用的方法没有具体的实现,因此我们接下来分析它的子类DaoAuthenticationProvider

retrieveUser()

protected final UserDetails retrieveUser(String username,

UsernamePasswordAuthenticationToken authentication)

throws AuthenticationException {

prepareTimingAttackProtection();

try {

(1) UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

if (loadedUser == null) {

throw new InternalAuthenticationServiceException(

"UserDetailsService returned null, which is an interface contract violation");

}

return loadedUser;

}

catch (UsernameNotFoundException ex) {

mitigateAgainstTimingAttack(authentication);

throw ex;

}

catch (InternalAuthenticationServiceException ex) {

throw ex;

}

catch (Exception ex) {

throw new InternalAuthenticationServiceException(ex.getMessage(), ex);

}

}

(1)处调用DaoAuthenticationProvider成员变量UserDetailsService的方法loadUserByUsername()从数据库中加载用户详细信息(用过Security的对此处应该是很熟悉了)

additionalAuthenticationChecks()

protected void additionalAuthenticationChecks(UserDetails userDetails,

UsernamePasswordAuthenticationToken authentication)

throws AuthenticationException {

if (authentication.getCredentials() == null) {

logger.debug("Authentication failed: no credentials provided");

throw new BadCredentialsException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.badCredentials",

"Bad credentials"));

}

(1) String presentedPassword = authentication.getCredentials().toString();

(2) if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {

logger.debug("Authentication failed: password does not match stored value");

throw new BadCredentialsException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.badCredentials",

"Bad credentials"));

}

}

(1)处从UsernamePasswordAuthenticationToken中调出了密码,再由(2)处通过调用成员变量passwordEncoder对其密码进行验证。

以上就是AuthenticationManager的验证大致流程,由于本人能力有限,如有错误,还请各位大佬多多包涵并在评论区进行留言指正,我会一一回复。

oracle access manager token,AuthenticationManager验证原理相关推荐

  1. oracle access manager token,Laravel 自带的 API 守卫驱动 token 使用详解

    在Laravel框架中,默认的用户认证守卫有两个,web和api,web守卫默认的驱动是session,而api守卫默认的驱动是token.那么,该如何使用这个token驱动? 寻找 TokenGua ...

  2. AuthenticationManager验证原理分析

    本文来说下AuthenticationManager验证原理 文章目录 AuthenticationManager概述 AuthenticationManager相关类图 security认证流程 A ...

  3. spring boot 集成 Oracle Access Manager(OAM)单点登录

    1.介绍 Oracle Access Manager(OAM)是oracle公司开发的身份认证和资源管理解决方案.结合WebGate和OHS可实现系统间单点登录集成. oracle中间件产品可以非常方 ...

  4. oracle不能访问管理页面,无法登录Oracle Access Manager的OAM控制台

    登录后我无法访问OAMConsole主页 . OAM安装在Centos 6.4 Server Edition上,我通过FireFox访问控制台 . 我可以毫无问题地访问控制台和企业管理器 . 但是,当 ...

  5. 【转】基于Token的身份验证原理

    目录 1 发展史 2 Cookie 3 Session 3.1 cookie和session的区别 4 Token 4.1 传统方式--基于服务器的验证 4.2 基于服务器验证方式暴露的一些问题 4. ...

  6. shopnc 支持 支付宝快捷登陆 shopnc权限验证原理说明

    为目前使用的是shopnc商场二次开发,shopnc本身做了qq互联和微博快捷登陆的api,做成了集成通用的接口 首先说下基本的这种类型的api访问方式,首先,的有个配置文件,配置你申请的id和key ...

  7. FLASK RESTFUL TOKEN用户验证笔记

    笔记1 REST要求无状态,可以理解为没有session,而且session的存储遇到分布式集群的情况就比较难搞,所以对于用户验证目前网上大多数做法是token方式,第一次登录的时候,先提交用户名密码 ...

  8. Oracle Enterprise Manager Cloud Control 13c部署文档

    一.软件准备 名称 版本号 软件包 OS 7.6 CentOS Linux release 7.6.1810 (Core) Oracle Database 19c 19.3 LINUX.X64_193 ...

  9. ibm tivoli_IBM Tivoli Access Manager:WebSEAL Kerberos连接

    ibm tivoli 总览 IBM®Tivoli®Access Manager(TAM)v6.1引入了WebSEAL的Kerberos联结的概念. Kerberos联结可用于对已启用Kerberos身 ...

最新文章

  1. Nature Commun. | 假单胞和芽孢杆菌互作共存于寄主的机制,为生物防治提供新思路...
  2. matlab jdbc mysql_Matlab连接MySQL(ODBC及JDBC驱动)
  3. 小坑记录:get_cmap参数区分大小写
  4. SQLAlchemy之SQL Expression
  5. c/c++函数指针(Hook前奏1)
  6. 4.2.4 磁盘的管理
  7. 腾讯数平精准推荐 | 横扫ICDAR 2019,斩获七项冠军
  8. 苍溪师范94级计算机与文秘专业就业前景,文秘专业就业前景
  9. Java标签移动_如何使用基于鼠标单击的标签移动特定游戏对象?
  10. android初步ui线程案例,android – 它是一个bug还是一个功能?在某些情况下,可以从未在UI线程上运行的任务访问UI线程...
  11. 2018.3.10 模拟赛——(2)给出字符串
  12. 可拖动的进度条_视频号全新升级!进度条可拖动、支持@、支持转发......
  13. CSDN如何获取积分
  14. 什么是移动端,它和pc端有什么区别
  15. VB Dim的初识与相关用法
  16. 2019超导量子计算机最新报道,在高温超导体加持下,量子效应的量子计算机,或将迎来重大突破...
  17. 解决csv文件的换行问题
  18. 对timedelta64的理解
  19. 计算机监听的端口,侦听计算机的指定端口并分析其数据包,监听,本,解析
  20. NOIP2016 略解

热门文章

  1. Elasticsearch索引分析
  2. 本地操作HDFS报错:java.net.ConnectException: Call From KevinWong/192.168.16.1 to hadoop000:9000
  3. Flink分布式standalone部署方式(第二种方式)
  4. shutil模块,为什么说它是os模块的兄弟模块?
  5. 肝!一款基于 Python 语言的 Linux 资源监视器!
  6. 你们需要的数据集,都给准备好了!
  7. 13 个 Python 新手练级项目
  8. 深度学习入门笔记系列(三)——感知器模型和 tensorboard 的使用方法
  9. mysql ---- innodb-4-备份和恢复
  10. Flask 系列之 Blueprint