SpringSecurity6从入门到实战之登录表单的提交(源码级讲解,耐心看完)

SpringSecurity6从入门到实战之登录表单的提交(源码级讲解,耐心看完)

文接上回,当SpringSecurity帮我们生成了一个默认对象.本文继续对登录流程进行探索,我们如何通过账号密码进行表单的提交,SpringSecurity在这过程中又帮助我们做了什么

登录表单的提交的源码分析

在之前了解了为什么所有的请求都会进行认证操作,我们也直接把目光放到源码中这个地方defaultSecurityFilterChain()

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
class SpringBootWebSecurityConfiguration {
    SpringBootWebSecurityConfiguration() {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingBean(
        name = {"springSecurityFilterChain"}
    )
    @ConditionalOnClass({EnableWebSecurity.class})
    @EnableWebSecurity
    static class WebSecurityEnablerConfiguration {
        WebSecurityEnablerConfiguration() {
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnDefaultWebSecurity
    static class SecurityFilterChainConfiguration {
        SecurityFilterChainConfiguration() {
        }

        @Bean
        @Order(2147483642)
        SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
            ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)http.authorizeHttpRequests().anyRequest()).authenticated();
            //这里就是进行表单登录的入口方法了
            http.formLogin();
            http.httpBasic();
            return (SecurityFilterChain)http.build();
        }
    }
}

我们进入formLogin()中继续看,可以看到这个方法formLogin().这里创建了一个FormLoginConfigurer,我们继续顺着这个构造方法进去看看

	public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
		return getOrApply(new FormLoginConfigurer<>());
	}
public FormLoginConfigurer() {
		super(new UsernamePasswordAuthenticationFilter(), null);
		usernameParameter("username");
		passwordParameter("password");
	}

这里可以看到FormLoginConfigurer调用了父类构造并且传了一个UsernamePasswordAuthenticationFilter对象,之前有介绍过这个过滤器是专门做账号密码认证的,那我们继续看向这个过滤器new UsernamePasswordAuthenticationFilter()

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //首先判断是否为POST请求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            //通过过去用户名和密码进行非空判断
            String username = this.obtainUsername(request);
            username = username != null ? username.trim() : "";
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
            //组装成一个UsernamePasswordAuthenticationToken令牌对象,方便传递
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            //这里将令牌对象传入,继续看看authenticate()做了什么
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

最终发现这个authenticate()是一个接口下的抽象方法,实际执行的是 AuthenticationManager 接口实现类 ProviderManager 中的 authenticate() 方法,在该方法中调用 AuthenticationProvider 接口的authenticate() 方法:

我们继续看:

image.png

image.png

可以发现这里传入了authentication对象最终返回的还是authentication对象,说明这里肯定为这个对象的其他属性进行了操作,我们继续看到provider.authenticate().

image.png

实际执行的是 AuthenticationProvider 接口实现类 AbstractUserDetailsAuthenticationProvider 中的 authenticate() 方法,在该方法中调用 retrieveUser() 方法:

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
        //第一次从缓存中获取user对象,肯定是找不到的
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
                //将输入的用户名和token对象传入retrieveUser()方法,最终返回了UserDetails
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}
		try {
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
			if (!cacheWasUsed) {
				throw ex;
			}
			// 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);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

向下查询retrieveUser(),由于retrieveUser()是抽象方法而当前类有且只有一个子类所以直接看到AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider中实现的retrieveUser()

image.png

通过username去加载用户,也是看到这个this.getUserDetailsService().loadUserByUsername().可以看到loadUserByUsername还是一个接口

package org.springframework.security.core.userdetails;

/**
 * Core interface which loads user-specific data.
 * <p>
 * It is used throughout the framework as a user DAO and is the strategy used by the
 * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider
 * DaoAuthenticationProvider}.
 *
 * <p>
 * The interface requires only one read-only method, which simplifies support for new
 * data-access strategies.
 *
 * @author Ben Alex
 * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
 * @see UserDetails
 */
public interface UserDetailsService {

	/**
	 * Locates the user based on the username. In the actual implementation, the search
	 * may possibly be case sensitive, or case insensitive depending on how the
	 * implementation instance is configured. In this case, the <code>UserDetails</code>
	 * object that comes back may have a username that is of a different case than what
	 * was actually requested..
	 * @param username the username identifying the user whose data is required.
	 * @return a fully populated user record (never <code>null</code>)
	 * @throws UsernameNotFoundException if the user could not be found or the user has no
	 * GrantedAuthority
	 */
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

实际执行的是 UserDetailsService 接口实现类 InMemoryUserDetailsManager 中的 loadUserByUsername() 方法,在该方法中会在 users 集合变量中根据用户输入的帐号获取 UserDetails 信息:

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		UserDetails user = this.users.get(username.toLowerCase());
		if (user == null) {
			throw new UsernameNotFoundException(username);
		}
		return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
				user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
	}

类 InMemoryUserDetailsManager 是由内存 map 支持的接口实现类,基于内存存储,不需要后端数据库

image.png

最终结论

总结:1. 默认用户名 user 和 控制台的密码,是在 SpringSecurity 提供的 User 类中定义生成的;

        2.在表单认证时,基于 InMemoryUserDetailsManager 类具体进行实现,也就是基于内存的实现。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

未来已至!OpenAI领航:日产千亿单词,5-7万亿AI芯片巨资揭秘,人类语言产出将被超越?

OpenAI每日狂飙&#xff0c;产出千亿单词&#xff01;他们的野心不止于此&#xff0c;未来竟想超越全球人类每日的百万亿单词产量。 而支撑这一切的&#xff0c;是一个震撼天地的5至7万亿美元的AI芯片投资大计。你能想象吗&#xff1f;这比许多国家的GDP还要高&#xff01; 想…

docker bash: vi: command not found 修改文件无法使用 vi yum的方法

如题&#xff0c;被入坑很多次。也参考了很多的修复docker 中的vi yum等方法。最终都未解决。 因为要修改 已安装容器中的各类配置信息。无法使用vi yum很麻烦。除去使用docker 挂载文件方法外&#xff0c;还可以使用如下方法直接修改对应的配置文件信息。 如: 修改 logstas…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于两阶段鲁棒的多综合能源微网-共享储能电站协同优化运行策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

奥威BI零售数据分析方案的优缺点一览

奥威BI零售数据分析方案是一套基于BI大数据智能可视化分析系统&#xff0c;根据零售企业数据分析共性需求、业务特殊性量身打造&#xff0c;点击下载应用&#xff0c;立即将零售数据情况分析清楚&#xff0c;直观呈现。很多企业都是直接在该零售数据分析方案的基础上实现了智能…

Vue3【六】setup的使用和setup的返回值

Vue3【六】setup的使用和setup的返回值 setup函数的使用&#xff0c;和vue2的选项式不同 vue3的组合式使用的是setup函数 通过返回值将数据和方法传到页面 返回值也可以是一个箭头函数 setup先于 data和method执行所有无法读取到this和data&#xff0c;method的内容&#xff0c…

FuTalk设计周刊-Vol.042

&#x1f525;AI漫谈 热点捕手 1、百川智能上新超千亿大模型Baichuan 3&#xff0c;冲榜成绩&#xff1a;若干中文任务超车GPT-4 发布了超千亿参数的最新版本大模型Baichuan 3&#xff0c;是百川智能基础模型第三代——就在20天前&#xff0c;这家由王小川创办的大模型公司&a…

WeTrade亮相Traders Fair展会菲律宾站

2024年5月25日&#xff0c;菲律宾交易博览会在马尼拉的Edsa香格里拉酒店圆满落幕。 WeTrade作为本次交易博览会的重要战略合作伙伴、参展商和赞助商&#xff0c;吸引了全球各界人士的广泛关注。 现场&#xff0c;我们的菲律宾团队与客户进行了亲密的面对面交流&#xff0c;并…

人工智能在肿瘤预后预测中的最新研究进展|顶刊精析·24-06-07

小罗碎碎念 今天要分享的文献主题&#xff0c;大家一定非常熟悉&#xff0c;因为绝大多数AI4cancer的文章都会提到它——预后预测&#xff0c;所以今天的文献主题是——人工智能肿瘤预后预测。 在正式开始分享之前&#xff0c;我想先带着大家梳理两个问题。解决了以下两个问…

【吊打面试官系列】简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别 ?

大家好&#xff0c;我是锋哥。今天分享关于 【简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别 &#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别 &#xff1f; MyISAM&#xff1a; 不支持事务&#xff0c;但是…

基于Python+FFMPEG环境下载B站歌曲

题主环境 WSL on Windows10 命令如下 # python3.9 pip install --pre yutto yutto --batch https://www.bilibili.com/video/BV168411o7Bh --audio-only ls | grep aac | xargs -I {} ffmpeg -i {} -acodec libmp3lame {}.mp3WinAmp

layui左侧菜单栏,鼠标悬停显示菜单文字

layui封装的左侧菜单是固定宽度的&#xff0c;且左侧菜单栏在css里改变宽度&#xff0c;效果并不是很好&#xff08;还设计头部菜单栏&#xff09;&#xff0c;如果写js来让菜单栏能够拉伸&#xff0c;也比较麻烦&#xff0c;那怎么最简单的&#xff0c;让用户看到菜单的文字呢…

Android 动态修改APP图标

文章目录 Android 动态修改APP图标定义activity-alias修改图标和App名监听APP前后台状态切换进入后台时切换修改图标和名字缺点 Android 动态修改APP图标 修改前&#xff1a; 修改后&#xff1a; 定义activity-alias 在 AndroidManifest.xml 中设置 activity-alias&#xff1…

【算法无用系列】电影推荐——余弦相似度计算用户相似度原理

【算法无用系列】通过余弦相似度计算电影、用户相似度 话不多说&#xff0c;本文通过电影推荐系统中&#xff0c;基于余弦相似度算法计算出用户相似和电影相似原理。希望可以帮助一些代码不懂的同学一些思路。 记录用户电影评分数据 一般情况来说&#xff0c;会根据用户的行为…

《贫穷的本质》

穷人获取的信息有限&#xff0c;存在认知上的差距&#xff0c;不了解自己现有的资源&#xff0c;并且合理使用。 self conclusion 1、由以下摘抄1有感而发&#xff1a;童年时期将很大程度上影响未来的发展。 《贫穷的本质》一书告诉我们&#xff0c;孕妇和幼儿时期如果能提供更…

【全开源】CRM管理客户关系系统源码

CRM&#xff1a;助力企业高效管理客户关系 全面解决企业销售团队的全流程客户服务难题&#xff0c;旨在助力企业销售全流程精细化、数字化管理&#xff0c;全面解决企业销售团队的全流程客户服务难题&#xff0c;帮助企业有效盘活客户资源、量化销售行为&#xff0c;合理配置资…

【C语言】学生管理系统:完整模拟与实现

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 &#x1f525;引言 本篇文章为修改了在校期间实训报告&#xff0c;使用C…

使用Ollama+OpenWebUI部署和使用Phi-3微软AI大模型完整指南

&#x1f3e1;作者主页&#xff1a; 点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月6日23点50分 &#x1f004;️文章质量&#xff1a;96分 欢迎来到Phi-3模型的奇妙世界&#xff01;Phi-3是由微软…

Linux通过安装包配置环境变量(详细教程)

本章教程使用jdk1.8.0_241版本在Linux CentOS系统中,配置Java环境变量。 一、下载安装包 微云下载:https://share.weiyun.com/JeWZMDoh 二、上传安装包 将安装包上传到linux中的opt目录中 三、解压安装包 tar -xzvf jdk-8u241-linux-x64.tar.gz四、配置环境变量 vim /etc/p…

CC++内存管理【new和delete操作符的详细分析】【常见面试题】

C/C内存管理 1.C/C内存分布 我们先来看一段代码&#xff0c;来了解一下C/C中的数据内存分布。 # include <stdlib.h>int globalVar 1; static int staticGlobalVar 1; // 比globalVar还要先销毁,同一个文件下后定义的先析构 // 全局变量存在 数据段&#xff08;静态…

【Linux】ip命令详解

Linux网络排查 目录 一、ip命令介绍 1.1 ip命令简介 1.2 ip命令的由来 二、ip命令使用帮助 2.1 ip命令的help帮助信息 2.2 ip命令对象介绍 2.3 ip命令选项介绍 三、查看网络信息 3.1 显示当前网络接口信息 3.2 显示网络设备运行状态 3.3 显示详细设备信息 3.4 查看…