SpringSecurity原理解析(二):认证流程

news2024/9/17 4:38:00

1、SpringSecurity认证流程包含哪几个子流程?

      1)账号验证

      2)密码验证

      3)记住我—>Cookie记录

      4)登录成功—>页面跳转

2、UsernamePasswordAuthenticationFilter

      在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤

      器中实现的,UsernamePasswordAuthenticationFilter 继承于 

      AbstractAuthenticationProcessingFilter 这个父类。

      当请求进来时,在doFilter 方法中会对请求进行拦截,判断请求是否需要认证,若不需要

      认证,则放行;否则执行认证逻辑;

              1

               

      注意:UsernamePasswordAuthenticationFilter 类中是没有 doFilter 方法的,doFilter

      方法是继承自父类 UsernamePasswordAuthenticationFilter 的。

            doFilter 方法代码如下:

//执行过滤的方法,所有请求都走这个方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        //请求和应答类型转换
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        //判断请求是否需要认证处理,若不需要认证处理,则直接放行
        if (!this.requiresAuthentication(request, response)) {
            //放行,往下走
            chain.doFilter(request, response);
        } else {
            //执行到这里,进行认证处理
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }

            Authentication authResult;
            try {
                //处理认证,然后返回 Authentication 认证对象
                //重点
                authResult = this.attemptAuthentication(request, response);
                //认证失败
                if (authResult == null) {
                    return;
                }
                //认证成功之后注册session
                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }
             
            //
            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
            //认证成功后的处理
            this.successfulAuthentication(request, response, chain, authResult);
        }
    }


protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
        }
        //将认证成功后的对象保存到 SecurityContext中
        SecurityContextHolder.getContext().setAuthentication(authResult);
        //处理 remember-me属性
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        //认证成功后,页面跳转
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }


              

      上边的核心代码是下边这一行:

             

       attemptAuthentication方法的作用是获取Authentication对象其实就是对应的认证过程,

       attemptAuthentication 方法在子类UsernamePasswordAuthenticationFilter 中实现的。

        attemptAuthentication 方法代码如下:

//认证逻辑
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //如果我们设置了该认证请求只能以post方式提交,且当前请求不是post请求,表示当前请求不符合
        //认证要求,直接抛出异常,认证失败
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            //执行到这里表示开始执行认证逻辑

            //从请求中获取用户名和密码
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            //将用户名和密码包装成 UsernamePasswordAuthenticationToken  对象
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            //设置用户提交的信息到 UsernamePasswordAuthenticationToken  中
            this.setDetails(request, authRequest);
            //getAuthenticationManager():获取认证管理器
            //authenticate:真正处理认证的方法
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

        1、

3、AuthenticationManager

     AuthenticationManager接口中就定义了一个方法authenticate方法,用于处理认证的请求;

     AuthenticationManager 接口定义如下:

public interface AuthenticationManager {

    //处理认证请求
	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

    

      在这里AuthenticationManager的默认实现是ProviderManager.而在ProviderManager的

      authenticate方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers

      。该providers中如果有一个AuthenticationProvider的supports函数返回true,那么就会调

      用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个认证过程结束。

      如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证

      成功则为认证成功。

      authenticate 方法定义如下:

             

//执行认证逻辑
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
        //获取 Authentication 对象的类型
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();

        //getProviders():获取系统支持的各种认证方式,如:QQ、微信、微博等等
		for (AuthenticationProvider provider : getProviders()) {
            //判断当前的 provider认证处理器 是否支持当前请求的认证类型,若不支持,则跳过
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}
            //执行到这里说明当前认证处理器支持当前请求的认证,

			try {
                //执行认证操作
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				//。。。。。省略 。。。。。
			}
			catch (InternalAuthenticationServiceException e) {
				//。。。。。省略 。。。。。
			}
			catch (AuthenticationException e) {
				//。。。。。省略 。。。。。
			}
		}

        //如果循环结束后还没找到支持当前请求的认证处理器provider ,且父类不为空,则
        //尝试调用父类的认证方法进行认证处理
		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				//。。。。。省略 。。。。。
			}
			catch (AuthenticationException e) {
				//。。。。。省略 。。。。。
			}
		}

        //清空密码凭证
		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}

			//
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// 
        //异常处理
		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		//
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}

       在上边的代码中,我们重点看的是下边这一行:

              result = provider.authenticate(authentication);

       因为是用户认证,所以这里authenticate方法走是AbstractUserDetailsAuthenticationProvider

       类中的实现,

      AbstractUserDetailsAuthenticationProvider.authenticate 方法定义如下所示:

//认证操作
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// 获取提交的账号
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

        //标记,是否使用缓存,默认是使用的,先从缓存中查找提交的账号
        //若账号已经登录,则缓存中应该
		boolean cacheWasUsed = true;
        //根据账号名称从缓存中查找账号,若缓存中不存在该账号,则需要认证
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {//若缓存中不存在该账号,没有缓存
			cacheWasUsed = false;

			try {
                //账号认证
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				//。。。。。省略 。。。。。
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
            //如果账号存在,即账号认证成功,则这里就开始密码认证
            //密码校验前的前置检查,检查账号是否过期、是否锁定等
			preAuthenticationChecks.check(user);
            //密码校验
            //user: 数据库中的数据
            //authentication: 表单提交的数据
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			//。。。。。省略 。。。。。
		}

        //检查凭证是否过期
        postAuthenticationChecks.check(user);
       
        //将用户保存到缓存中
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
}

//创建具体的 Authentication 对象
protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		
		// user.getAuthorities():返回用户的权限
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());

		return result;
	}

              密码前置校验如下图所示:

                      

      然后进入到retrieveUser方法中,retrieveUser和additionalAuthenticationChecks 方法

      具体的实现是DaoAuthenticationProvider 类中实现的,如下所示

@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
            // getUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了
			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) {
			//。。。。。省略 。。。。。
		}
		catch (InternalAuthenticationServiceException ex) {
			//。。。。。省略 。。。。。
		}
		catch (Exception ex) {
			//。。。。。省略 。。。。。
		}
	}

//具体的密码校验逻辑
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"));
		}

        //获取表单提交的密码
		String presentedPassword = authentication.getCredentials().toString();

        //拿表单提交的密码,与数据库中的密码进行匹配,若匹配失败,则抛出异常
		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"));
		}
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iOS——线程安全、线程同步与线程通信

线程安全和线程同步 线程安全&#xff1a;如果你的代码所在的进程中有多个线程在同时运行&#xff0c;而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的&#xff0c;而且其他的变量的值也和预期的是一样的&#xff0c;就是线程安全的。 若每个…

18055 主对角线上的元素之和

### 思路 1. 输入一个3行4列的整数矩阵。 2. 计算主对角线上的元素之和。 3. 输出主对角线上的元素之和。 ### 伪代码 1. 初始化一个3行4列的矩阵 matrix。 2. 输入矩阵的元素。 3. 初始化一个变量 sum 为0&#xff0c;用于存储主对角线元素之和。 4. 遍历矩阵的行&#xff0c…

【Day08-IO-文件字节流】

File 1. 概述 File对象既可以代表文件、也可以代表文件夹。它封装的对象仅仅是一个路径名&#xff0c;这个路径可以存在&#xff0c;也可以不存在 构造器 说明 public File​(String pathname) 根据文件路径创建文件对象 public File​(String parent, String child) 根据…

vscode中使用go环境配置细节

1、在docker容器中下载了go的sdk 2、在/etc/profile.d/go.sh里填入如下内容: #!/bin/bashexport GOROOT=/home/ud_dev/go export PATH=$GOROOT/bin:$PATH 3、设置go env go env -w GOPROXY=https://goproxy.cn,direct go env -w GO111MODULE=on 4、重启这个容器,使得vscod…

DBAPI如何使用内存缓存

背景 在使用DBAPI创建API的时候&#xff0c;有时候SQL查询比较耗时&#xff0c;如果业务上对数据时效性要求不高&#xff0c;这种耗时的SQL可以使用缓存插件来将数据缓存起来&#xff0c;避免重复查询。 一般来说&#xff0c;可以使用redis memcache等缓存服务来存储缓存数据。…

活动|华院计算宣晓华受邀出席“AI引领新工业革命”大会,探讨全球科技的最新趋势

8月31日&#xff0c;“AI引领新工业革命”大会于上海图书馆圆满落幕。本次大会由TAA校联会和台协科创工委会联合主办&#xff0c;得到上海市台办、上海市台联、康师傅的大力支持。大会邀请了NVIDIA全球副总裁、亚太区企业营销负责人刘念宁&#xff0c;元禾厚望资本创始合伙人潘…

ispunct函数讲解 <ctype.h>头文件函数

目录 1.头文件函数 2.ispunct函数使用 小心&#xff01;VS2022不可直接接触&#xff0c;否则..!没有这个必要&#xff0c;方源一把抓住VS2022&#xff0c;顷刻 炼化&#xff01; 1.头文件函数 以上函数都需要包括头文件<ctype.h> &#xff0c;其中包括 ispunct 函数 #…

esp8266+sg90实现远程开关灯(接线问题)

1需要准备的设备 首先需要的设备 硬件&#xff1a;esp8266开发板和sg90舵机&#xff0c;还有公对母的杜邦线&#xff0c;以及一根usb程序下载线。 软件&#xff1a;Arduino IDE 因为sg90舵机接口是三个连着的&#xff0c;只能用公对母的杜邦线把三条信号线接到esp8266的不同引…

Linux驱动.之字符设备驱动框架,新内核框架,设备树(二)

第一篇比较长&#xff0c;第二篇&#xff0c;继续写&#xff0c;内容有重复 一、字符设备驱动框架 在用户空间中调用open&#xff0c;打开一个字符设备&#xff0c;执行流程如下&#xff1a;最终会执行chrdev中的ops对应的open函数。

【python计算机视觉编程——8.图像内容分类】

python计算机视觉编程——8.图像内容分类 8.图像内容分类8.1 K邻近分类法&#xff08;KNN&#xff09;8.1.1 一个简单的二维示例8.1.2 用稠密SIFT作为图像特征8.1.3 图像分类:手势识别 8.2贝叶斯分类器用PCA降维 8.3 支持向量机8.3.2 再论手势识别 8.4 光学字符识别8.4.2 选取特…

面试官:你是怎么处理vue项目中的错误的?

一、错误类型 任何一个框架&#xff0c;对于错误的处理都是一种必备的能力 在Vue 中&#xff0c;则是定义了一套对应的错误处理规则给到使用者&#xff0c;且在源代码级别&#xff0c;对部分必要的过程做了一定的错误处理。 主要的错误来源包括&#xff1a; 后端接口错误代…

网络原理之TCP协议(万字详解!!!)

目录 前言 TCP协议段格式 TCP协议相关特性 1.确认应答 2.超时重传 3.连接管理&#xff08;三次握手、四次挥手&#xff09; 三次握手&#xff08;建立TCP连接&#xff09; 四次挥手&#xff08;断开连接&#xff09; 4.滑动窗口 5.流量控制 6.拥塞控制 7.延迟应答…

(入门篇)JavaScript 网页设计案例浅析-简单的交互式图片轮播

网页设计已经成为了每个前端开发者的必备技能,而 JavaScript 作为前端三大基础之一,更是为网页赋予了互动性和动态效果。本篇文章将通过一个简单的 JavaScript 案例,带你了解网页设计中的一些常见技巧和技术原理。今天就说一说一个常见的图片轮播效果。相信大家在各类电商网…

使用vscode上传git远程仓库流程(Gitee)

目录 参考附件 git远程仓库上传流程 1&#xff0c;先将文件夹用VScode打开 2&#xff0c;第一次进入要初始化一下仓库 3&#xff0c;通过这个&#xff08;.gitignore&#xff09;可以把一些不重要的文件不显示 注&#xff1a;&#xff08;.gitignore中&#xff09;可屏蔽…

AI辅助编程里的 Atom Group 的概念和使用

背景 在我们实际的开发当中&#xff0c;一个需求往往会涉及到多个文件修改&#xff0c;而需求也往往有相似性。 举个例子&#xff0c;我经常需要在 auto-coder中需要添加命令行参数&#xff0c;通常是这样的&#xff1a; /coding 添加一个新的命令行参数 --chat_model 默认值为…

基于RAG和知识库的智能问答系统设计与实现

开局一张图&#xff0c;其余全靠编。 自己画的图&#xff0c;内容是由Claude根据图优化帮忙写的。 1. 引言 在当今数字化时代&#xff0c;智能问答系统已成为提升用户体验和提高信息获取效率的重要工具。随着自然语言处理技术的不断进步&#xff0c;特别是大型语言模型&#x…

Sonarqube 和 Sonar-scanner的安装和配置

SonarQube 简介 所谓sonarqube 就是代码质量扫描工具。 官网&#xff1a; https://www.sonarsource.com/sonarqube/ 在个人开发学习中用处不大&#xff0c; 我草&#xff0c; 我的代码质量这么高需要这玩意&#xff1f; 但是在公司项目中&#xff0c; 这个可是必须的&#x…

【高校主办,EI稳定检索】2024年人机交互与虚拟现实国际会议(HCIVR 2024)

会议简介 2024年人机交互与虚拟现实国际会议&#xff08;HCIVR 2024&#xff09;定于2024年11月15-17日在中国杭州召开&#xff0c;会议由浙江工业大学主办。人机交互&#xff0c;虚拟现实技术的发展趋势主要体现在系统将越来越实际化&#xff0c;也越来越贴近人类的感知和需求…

心觉:第一性原理思考和共情能力,怎么用效果更好

Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作163/1000天 我很佩服逻辑能力很强的人 也很佩服共情能力很强的人 他们都很厉害 我自己感觉逻辑能力更强一点&#xff0c;平时喜欢…

strlen函数模拟实现(嵌套函数调用)

目录 1.模拟实现strlen函数代码&#xff08;嵌套函数&#xff09; 2.代码解释 小心&#xff01;VS2022不可直接接触&#xff0c;否则..!没有这个必要&#xff0c;方源一把抓住VS2022&#xff0c;顷刻 炼化&#xff01; 1.模拟实现strlen函数代码&#xff08;嵌套函数&#x…