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

news2024/10/6 20:28:39

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 类具体进行实现,也就是基于内存的实现。

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

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

相关文章

SITNE24V2BNQ-3/TR一种瞬态电压抑制器,对标PESD1CAN

SITNE24V2BNQ是一种瞬态电压抑制器&#xff0c;设计用于保护两个汽车控制器区域 网络(CAN)母线不受ESD等瞬变造成的损坏。 SITNE24V2BNQ采用SOT-23封装。标准产品不含铅和卤素。 产品参数 方向&#xff1a;双向通道数&#xff1a;2VRWM(V)(Max)&#xff1a;24IPP8/20μS(A)(M…

cad转换为空间数据库方案

autodesk cad 通过另存为dxf格式 如 dxf2010 或者dxf2012。 再通过supermap desktop 软件 可以转换为arcgis esri shape arcgis esri shape 可以用arcgis打开做建库操作。 可以通过第二个个人工具&#xff0c;读取cad设置的颜色&#xff0c;达到数据颜色gis中和cad中一致。 …

金鸣识别:助您快速识别竖排图片文字

大家有没有遇到过这种情况&#xff1f; 当你手捧一本古籍&#xff0c;或是浏览某些特殊的书籍时&#xff0c;文字却是从右到左竖向排版的。这种排版方式&#xff0c;仿佛让我们穿越到了古代&#xff0c;感受到了那种独特的韵味。但问题是&#xff0c;一般的OCR软件根本不支持这…

为何瑞士银行成了富人的“保险箱”?

​瑞士银行&#xff0c;这个名字大家耳熟能详&#xff0c;为啥呢&#xff1f;因为它被誉为“全球最安全银行”。那么&#xff0c;这“最安全”的名头是怎么来的呢&#xff1f;它的金库又藏在哪儿呢&#xff1f; 话说在1930年代&#xff0c;德国纳粹迫害犹太人&#xff0c;导致…

RBAC 模型梳理

1. RBAC 模型是什么 RBAC&#xff08;Role-Based Access Control&#xff09;即&#xff1a;基于角色的权限控制。通过角色关联用户&#xff0c;角色关联权限的方式间接赋予用户权限。 RBAC 模型由 4 个基础模型组成&#xff1a; 基本模型 RBAC0&#xff08;Core RBAC&#x…

Qwen2本地部署的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

归并排序(分治)

归并排序 概念介绍原理解释&#xff1a;案例步骤&#xff1a;稳定性&#xff1a;画图理解如下 代码实现 概念介绍 归并排序&#xff08;Merge Sort&#xff09;是一种经典的排序算法&#xff0c;基于分治的思想&#xff0c;它将待排序数组分成两个子数组&#xff0c;分别排序&…

12. ESP32-JSON(Arduino)

使用ESP32和Arduino框架处理JSON数据 在物联网&#xff08;IoT&#xff09;开发中&#xff0c;ESP32是一款功能强大的微控制器&#xff0c;它结合了Wi-Fi和蓝牙功能&#xff0c;适用于各种智能设备和传感器项目。JSON&#xff08;JavaScript Object Notation&#xff09;是一种…

C++三大特性之继承,详细介绍

阿尼亚全程陪伴大家学习~ 前言 每个程序员在开发新系统时&#xff0c;都希望能够利用已有的软件资源&#xff0c;以缩短开发周期&#xff0c;提高开发效率。 为了提高软件的可重用性(reusability)&#xff0c;C提供了类的继承机制。 1.继承的概念 继承&#xff1a; 指在现有…

储能逆变器测试负载箱是如何实现的

储能逆变器测试负载箱是专门用于模拟各种负载条件的设备&#xff0c;主要用于对储能逆变器进行性能测试和评估。它可以根据实际需求&#xff0c;模拟不同的负载类型、负载大小和负载变化率&#xff0c;从而为储能逆变器的设计和优化提供准确的数据支持。那么&#xff0c;储能逆…

关于gitee上传成功没有绿点

今天上传完代码以后&#xff0c;打开gitee看了一下&#xff0c;但是看到昨天和今天都没有小绿点&#xff0c;仔细思考了一番&#xff0c;是仓库满了&#xff1f;不对啊&#xff0c;如果满了的话&#xff0c;上传就会失败啊&#xff0c;那这是什么问题呢&#xff1f; 原来是因为…

20240607在Toybrick的TB-RK3588开发板的Android12下适配IMX415摄像头和ov50c40

20240607在Toybrick的TB-RK3588开发板的Android12下适配IMX415摄像头和ov50c40 2024/6/7 11:42 【4K/8K摄像头发热量巨大&#xff0c;请做好散热措施&#xff0c;最好使用散热片鼓风机模式&#xff01;】 结论&#xff1a;欢迎您入坑。 Toybrick的TB-RK3588开发板的技术支持不…

AlaSQL.js:用SQL解锁JavaScript数据操作的魔法

简介 先附上 github 地址 https://github.com/AlaSQL/alasql AlaSQL.js - JavaScript SQL database for browser and Node.js. Handles both traditional relational tables and nested JSON data (NoSQL). Export, store, and import data from localStorage, IndexedDB, or …

从零开始学JAVA

一、编写Hello world程序 public class JavaMain1 {//主程序执行入口&#xff0c;main方法public static void main(String[] args){System.out.println("Hello world!");} } 运行结果 Hello world! java编写主程序常见错误&#xff1a; 1、System ---首字母没有…

vue2+elementui,动态生成的表单校验

话不多,先上一段视频,看看是不是你们需要的效果 elementui动态生成表单校验 附上代码 <template><div class"home"><div class"home-box"><!-- <menuHtml></menuHtml> --><div class"home-div"><…

使用2个手机文件恢复工具,轻松找回文件

在这个智能手机横行的时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。然而&#xff0c;就像生活中的一切事物一样&#xff0c;手机也有可能出现意外&#xff0c;比如文件丢失。这就像是你在超市购物&#xff0c;结果发现钱包不见了&#xff0c;那种感觉真是让人抓狂…

CTFHUB-技能树-web-web前置技能-HTTP协议全

目录 1.请求方式 2.302跳转 3.Cookie 4.基础认证 5.响应包源码 1.请求方式 curl -v -X http://challenge-3022c877a8dcedeb.sandbox.ctfhub.com:10800/index.php 2.302跳转 参考链接&#xff1a;http://t.csdnimg.cn/aqdNG 301——永久性重定向。该状态码表示请求的资源已…

攻防世界---misc---津门杯2021-m1

1、题目描述&#xff0c;下载附件是一张bmp格式的图片 2、直觉告诉我这和图片的颜色通道有关 3、于是我就尝试用stegslove打开图片 4、将颜色通道都改为0&#xff0c;点击preview 5、然后发现一串base64编码 6、解码得flag flag{l5DGqF1pPzOb2LU919LMaBYS5B1G01FD}

MyEclipse新手使用介绍

目录 1.MyEclipse诞生背景 2.作用 3.版本历史 4.优缺点 5.应用场景 6.如何使用 6.1.下载与安装 6.2.MyEclipse 菜单及其菜单项 7.创建和发布一个 Java 程序 7.1.创建 Java 程序 7.2.发布 Java 程序 8.示例 8.1. Hello World 示例 8.2. 简单Spring Boot 应用 8.3…

行心科技中禄松波携手,开启智能健康新时代

在2024年第34届健博会暨中国大健康产业文化节的盛大舞台上&#xff0c;广州市行心信息科技有限公司&#xff08;以下简称“行心科技”&#xff09;与浙江中禄松波生物工程有限公司&#xff08;以下简称“中禄松波”&#xff09;宣布达成战略合作&#xff0c;共同推动医康养产业…