一文上手SpringSecurity【三】

一、认证流程分析

上篇文章当中,我们一步一步查阅源码方式对认证流程有了一些认证,本章节梳理一下整个流程,最后形成一张图,以更直观的方式来理解认证的整个流程.

1.1 认证当中步及的接口和类

1.1.1 【抽象类】AbstractAuthenticationProcessingFilter

  • 实现了GenericFilterBean抽象类,GenericFilterBean实现了接口Filter
  • 此类当中重写了doFilter()方法,当请求到达的时候,要经过该过滤器,并且执行doFitler方法
  • 定义模板方法Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)

1.1.2 【实现类】UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter

  • 核心功能实现父类定义的模板方法

1.1.3 【接口】AuthenticationManager

  • 认证管理器接口
  • 提供了核心认证方法authenticate

1.1.4 【实现类】ProviderManager implements AuthenticationManager

  • 实现了接口当中的定义的抽象方法authenticate

1.1.5 【接口】AuthenticationProvider

  • 认证的提供方法, 可以处理特定 Authentication 实现
  • 提供了认证方法authenticate

1.1.6 【抽象类】AbstractUserDetailsAuthenticationProvider implement AuthenticationProvider

  • 实现了接口AuthenticationProvider定义的authenticate()方法
  • 定义模板方法UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication), 用于检索用户

1.1.7 【实现类】DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider

  • 实现父类定义的模板方法, 完成用户的检索

1.1.8 【接口】UserDetailsService

  • 定义抽象方法:UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
  • 该方法作用: 根据用户名称在数据源当中里查询出具体用户信息, 并且封装为UserDetails对象

1.1.9 【实现类】InMemoryUserDetailsManager implements UserDetailsManager

  • 基于内存的数据存储,实现接口当中定义的方法loadUserByUsername

1.1.10 【接口】 UserDetails

  • 提供用户的核心信息封装
  • 包括用户名称、用户密码、权限列表集合、用户状态信息

1.1.11 【实现类】User implements UserDetails

  • 实现接口相关方法
  • 对认证的用户信息进行封装

1.1.12 【接口】Authentication

  • 定义了认证用户的主体信息
  • 权限信息、认证主体信息、用户凭证

1.1.13 【实现类】UsernamePasswordAuthenticationToken

  • 间接实现了接口Authentication
  • 用来将用户传递的用户名称、密码封装成Authentication对象
  • 存储用户名称的时候也是传入的此对象

1.1.14 【图】 认证图解

以上就是认证和授权当中所使用到的接口、抽象类和实现类.结合上篇文章的认证流程的源码分析,可以得出如下的认证时序图
1

二、默认密码处理流程

2.1 UserDetailsServiceAutoConfiguration自动装配

在上篇文章的认证流程的源码分析当中,从数据源当中获取UserDetails对象的时候,使用的是如下的代码

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

this.getUserDetailsService()返回UserDetailsService对象,问题题为什么是返回的InMemoryUserDetailsManager的对象呢?为什么不是其它对象呢?
2

其实在应用程序加载的时候,已经默认将InMemoryUserDetailsManager初始化好了,然后将其放到容器当中了.查看UserDetailsServiceAutoConfiguration源码.

@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@Conditional(MissingAlternativeOrUserPropertiesConfigured.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
public class UserDetailsServiceAutoConfiguration {// ....
}

其中核心方法如下所示:

// 将InMemoryUserDetailsManager对象放到容器当中
// InMemoryUserDetailsManager是将数据存储到内存当中.这也是spring security默认的存储策略
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,ObjectProvider<PasswordEncoder> passwordEncoder) {SecurityProperties.User user = properties.getUser();List<String> roles = user.getRoles();return new InMemoryUserDetailsManager(User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build());
}

2.2 默认用户名称生成策略

在UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager自动装配方法当中实例化InMemoryUserDetailsManager的时候, 设置的默认的用户名称和密码.

return new InMemoryUserDetailsManager(User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build());

其中User.withUsername(user.getName())表示设置用户名称
进入User.withUsername方法, 该方法表示设置用户名称

public static UserBuilder withUsername(String username) {return builder().username(username);
}

通过构建者模式,设置用用户名称,进入方法查看
3

public UserBuilder username(String username) {Assert.notNull(username, "username cannot be null");this.username = username;return this;
}

this.name=username,这个赋值语句, 用于将传入的用户名称存储到内存当中.

User.withUsername(user.getName()), user.getName()表示获取用户名称,查看该方法
4

public void setName(String name) {this.name = name;
}

直接返回this.name的值, 找到趸同变量name.

private String name = "user";

所以默认的用户名称为: user.

2.3 默认密码生成策略

在UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager自动装配方法当中实例化InMemoryUserDetailsManager的时候, 设置的默认的用户名称和密码.

return new InMemoryUserDetailsManager(User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build());

.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))将密码存储到从内存当中,调用了UserDetailsServiceAutoConfiguration#getOrDeducePassword方法获取密码

private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {String password = user.getPassword();if (user.isPasswordGenerated()) {user.getPassword()));}if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {return password;}return NOOP_PASSWORD_PREFIX + password;
}

user.getPassword()表示获取密码
6

public String getPassword() {return this.password;
}
private String password = UUID.randomUUID().toString();

默认采用的是uuid的方式生成的密码,最后将生成的用户名称和密码存储到内存当中.

三、自定义认证页面、用户名称和密码

3.1 自定义认证页面

在项目当中的resources/static目录下创建一个html文件,起名字: login.html即可.
5

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>登录</title>
</head>
<body><form method="post" action="/login">用户名称: <input type="text" name="username"><br>用户密码: <input type="password" name="password"><br><input type="submit" value="登录"></input></br></input></br></input></form>
</body>
</html>

特别注意: 这里只是替换了认证的登录页面,具体的认证流程处理还是由spring security默认的流程去处理即可.
怎么才能让spring security使用我们自己定义的认证页面呢? 在上篇文章的认证流程的源码分析当中,提到了spring security的自动装配, 如果想让自动装配失效,则必须要破坏自动装配的生效条件.

class DefaultWebSecurityCondition extends AllNestedConditions {DefaultWebSecurityCondition() {super(ConfigurationPhase.REGISTER_BEAN);}@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })static class Classes {}@ConditionalOnMissingBean({ SecurityFilterChain.class })static class Beans {}
}

@ConditionalOnMissingBean({ SecurityFilterChain.class }), 如果在容器当中,没有 SecurityFilterChain.class 这个bean对象,则默认配置生效,相反的说,如果我们自己定义了 SecurityFilterChain.class 对象,将它添加到容器当中,则默认配置失效,会执行我们自己的认证配置.

3.2 编写配置文件

@Configuration
public class SpringSecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {returnhttp.authorizeHttpRequests(authorize -> {// 如果请求的是/login.html则放行authorize.requestMatchers("/login.html").permitAll().anyRequest().authenticated(); // 其它的请求都需要认证}).formLogin(form -> { // 配置登录相关的信息// 用来指定默认的登录页面, 注意: 一旦自定义登录页面以后必须配置一下登录的url【必须】form.loginPage("/login.html").loginProcessingUrl("/login"); // ①. 指定处理登录的url【必须的】}).csrf(AbstractHttpConfigurer::disable).build();}
}

重点内容都添加到注释当中了,注意查看.
配置完成之后,启动项目,请求接口发现换成我们自己定义的登录页面了.
7
输入默认的用户名称及密码,成功访问接口
6

3.3 自定义密码

在spring security自动装配当中

@AutoConfiguration(before = UserDetailsServiceAutoConfiguration.class)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {@Bean@ConditionalOnMissingBean(AuthenticationEventPublisher.class)public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {return new DefaultAuthenticationEventPublisher(publisher);}
}

@EnableConfigurationProperties(SecurityProperties.class), 关于spring security的所有属性配置被引入了,我们查看一下SecurityProperties类当中,可以看到
9
在application.yml文件当中配置密码:

spring:security:user:name: byejackpassword: byejack

再次启动项目,访问接口
观察启动的控制台日志输出,可以发现,如果我们自己定义了密码,则spring security不再生成密码.
清除浏览器缓存之后,再次访问/hello接口
7
访问成功.
8

这里需要注意的是, 当认证成功一次,默认的spring security会将用户凭证保存到session当中,此时清除一下浏览器缓存,或者直接在浏览器当中,删除凭证,再次测试即可. 或者直接在配置文件当中关闭也可.
10

三、总结

3.1 内容总结

  • 认证、授权当中所用到的类或者接口
  • 认证流程图解
  • 默认的密码生成策略
  • 自定义认证登录页面、密码

3.2 下篇内容

  • 自定义认证页面细节
  • 自定义存储用户信息的数据源

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

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

相关文章

OFDM通信系统发射端需要做ifftshift的原因分析

对频率为15Hz的正弦波信号进行FFT分析&#xff0c;并且直接画图&#xff0c;matlab代码如下&#xff1a; fs 100; % sampling frequency t 0:(1/fs):(10-1/fs); % time vector S cos(2*pi*15*t); n length(S); X fft(S); f (0:n-1)*(fs/n); %frequenc…

使用canvas截取web camera指定区域,并生成图片

目标&#xff0c;截取红色色块背后的视频区域。 代码结构如下&#xff1a; <div id"p1"><video id"v1" autoplay playsinline></video><div id"mrz"></div><canvas id"captureCanvas"></can…

优化|深入解读DeepOPF:一种用于安全约束直流最优潮流问题的深度神经网络方法

原文信息&#xff08;包括题目、发表期刊、原文链接等&#xff09;&#xff1a; DeepOPF: A Deep Neural Network Approach for Security-Constrained DC Optimal Power Flow https://ieeexplore.ieee.org/document/9205647 原文作者&#xff1a;Xiang Pan; Tianyu Zhao; Ming…

机器学习-聚类

http://en.wikipedia.org/wiki/Multispectral_pattern_recognition 聚类基础知识 凝层次聚类 K-means 聚类 基于EM算法的聚类 聚类基础知识 聚类&#xff1a;将数据划分到不同的类里&#xff0c;使相似的数据在同一类里&#xff0c;不相似的数据在不同的类里&#xff08;物…

芝法酱学习笔记(0.5)——使用jenkins做自动打包

前言 上节讲了SpringBoot上的打包。但这些过程都是手动的&#xff0c;在实际的开发测试时&#xff0c;自动化的打包部署&#xff0c;可以大大提升团队开发的效率 一、去官网下载 1.1 官网安装命令 对于如何安装的问题&#xff0c;我向来推荐官网 wget -O /usr/share/keyri…

ThreeJs绘制圆柱体

上一章节实现了圆锥体的绘制&#xff0c;这节来绘制圆柱体&#xff0c;圆柱体就是矩形旋转获得&#xff0c;如上文一样&#xff0c;先要创建出基础的组件&#xff0c;包括场景&#xff0c;相机&#xff0c;灯光&#xff0c;渲染器。代码如下&#xff1a; initScene() {this.sce…

【vue-router】用meta.keepAlive做缓存

网上大家都说按下面的写法 <keep-alive><router-view v-if"route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if"!route.meta.keepAlive"></router-view>但是会报错 解决方法也没找到 最后换一…

Java项目实战II基于Java+Spring Boot+MySQL的学院班级回忆录(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 在时光的长河中&#xff0c;班级的记忆如同璀璨星辰&#xff0c;照亮了我们共同的青春岁月。为了珍藏…

鼎跃安全丨多功能气体检测报警系统:工业安全守护者

在工业快速发展的今天&#xff0c;各种复杂的生产环境中潜藏着诸多安全隐患。尤其在石油化工企业中&#xff0c;易燃易爆的气体随时可能引发危险&#xff1b;矿山作业里&#xff0c;有毒有害气体的风险更是持续不断&#xff1b;而制药等行业也面临着各类气体泄漏的风险。如何灵…

基于 LangChain 的自动化测试用例的生成与执行

在前面的章节中&#xff0c;分别介绍了 Web、App、接口自动化测试用例的生成。但是在前文中实现的效果均为在控制台打印自动化测试的用例。用例需要手动粘贴&#xff0c;调整之后再执行。 那么其实这个手动粘贴、执行的过程&#xff0c;也是可以直接通过人工智能完成的。 应用…

搭建基于H.265编码的RTSP推流云服务器

一、前言 网上能够找到的RTSP流地址&#xff0c;均是基于H.264编码的RTSP流地址&#xff0c;无法测试应用是否可以播放H265实时流为此&#xff0c;搭建本地的把H.264转码成H.265的RTSP服务器&#xff0c;不管是通过VLC搭建本地RTSP服务器&#xff0c;还是通过FFmpeg搭建本地RT…

Linux-du命令使用方法

Linux-du&#xff08;disk useage&#xff09;命令 du 命令用于查看文件和目录占用的磁盘空间。 du [选项] [文件或目录]-h (human-readable)&#xff1a; 将输出格式转为人类可读的形式&#xff0c;使用 KB、MB 等单位。 du -h /path/to/directory1.5M /path/to/directory…

如何在IDEA中使用Rainbow Fart

啥是Rainbow Fart GitHub上的中文README文件 安装 首先&#xff0c;我们在Setting的Plugins的Marketplace里搜索Rainbow Fart并install 这一步极其简单&#xff0c;我相信每个人都能做到&#xff0c;不详讲了。 配置 这是大部分小伙伴都想搞清楚的点&#xff0c;也不能说我…

研究生如何利用ChatGPT帮助开展日常科研工作?

小白可做&#xff01;全自动AI影视解说一键成片剪辑工具https://docs.qq.com/doc/DYnl6d0FLdHp0V2ll 作为当代研究生&#xff0c;科研工作三部曲----读文献、开组会、数据分析。无论哪一个&#xff0c;都令研究生们倍感头疼&#xff0c;简直就是梦魇。每当看到导师发来的消息&a…

JavaScript 中变量命名的最佳实践

全篇大概1500 字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间5分钟。 1. 避免使用 var 关键字&#xff1a;过时的产物 在现代 JavaScript 中&#xff0c;我们通常避免使用 var&#xff0c;而是选择 let 和 const&#xff0c;它们提供更可预测和块范围的行为&#x…

PTH原理 补丁+工具

顺着《域渗透攻防指南》4.9的总结记录下。 0x00 PTH简单说明 PTH在内网渗透中用于横向移动。由于NTLM && Kerberos都是采用用户密码的NTLM Hash&#xff0c;所以我们不需要非得拿用户明文口令&#xff0c;拿到hash一样可以。 拿到hash后&#xff0c;可以撞hash&…

C++不同的头文件中各种函数的操作使用(长期更新,找到新的就补充进来)

一、万能头文件 #include <bits/stdc.h> 万能头文件中包含的内容 // C #ifndef _GLIBCXX_NO_ASSERT #include <cassert> #endif #include <cctype> #include <cerrno> #include <cfloat> #include <ciso646> #include <climits> #in…

leetcode每日一题day17(24.9.27)——每种字符最少取k个

思路&#xff1a;看到题目就想到了搜索&#xff0c; 广搜&#xff1a;满足要求就往后搜&#xff0c;最后返回搜索队列达到过的最大深度&#xff0c; 深搜&#xff1a;一直往一边取&#xff0c;搜索完所有可能&#xff0c;并在此基础上进行剪枝&#xff0c;剪枝方案有如果某一分…

Windows环境部署Oracle 11g

Windows环境部署Oracle 11g 1.安装包下载2. 解压安装包3. 数据库安装3.1 执行安装脚本3.2 电子邮件设置3.3 配置安装选项3.4 配置系统类3.5 选择数据库安装类型3.6 选择安装类型3.7 数据库配置3.8 确认安装信息3.9 设置口令 Oracle常用命令 2023年10月中旬就弄出大致的文章&…

如何在Unity WebGL上实现一套全流程简易的TextureStreaming方案

项目介绍 《云境》是一款使用Unity引擎开发的WebGL产品&#xff0c;有展厅&#xff0c;剧本&#xff0c;Avatar换装&#xff0c;画展&#xff0c;语音聊天等功能&#xff0c;运行在微信小程序和PC&#xff0c;移动端网页&#xff0c;即开即用。 当前问题和现状 当前项目…