从HelloWorld深入源码了解SpringSecurity底层逻辑

news/2024/11/16 19:07:50/

文章目录

  • 一、环境搭建
    • 1、创建项目测试
      • 1.1、搭建基础项目
      • 1.2、整合Spring Security
  • 二、实现原理
    • 1、Spring Security的实现原理
      • 1.1、Spring Security 如何完成认证和授权
      • 1.2、Security Filters
    • 2、 Spring Security默认配置和如何自定义配置
  • 三、整个HelloWorld的流程分析
  • 三、HelloWorld中默认⽤户⽣成
  • 三、UserDetailService
  • 四、总结

对于任何的项目都需要从一个案例来分析出其中技术的门道。 Spring Security也是这样。

一、环境搭建

对于Spring Security的环境搭建基础是Spring Boot 2.7.12

1、创建项目测试

1.1、搭建基础项目

创建一个SpringBoot项目

 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version></parent>

为了测试方便,导入一个web项目

 <!--spring web的依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

最后写上Controller的测试代码:

@RestController
public class HelloController {@RequestMapping("/hello")public String hello(){System.out.println("hello security!!");return "hello security";}
}

在这里插入图片描述
最后运行项目测试结果得到在这里插入图片描述

1.2、整合Spring Security

引入Spring Security相关依赖

  <!--spring web的依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

导入成功之后,再次启动项目
在这里插入图片描述
启动完之后,控制台会生成一个密码。访问http://localhost:8080/hello,会直接跳到登录页面
在这里插入图片描述
默认的用户名是user,密码是控制台中打印的密码,输入之后可以成功进行访问。

这就是 Spring Security 的强⼤之处,只需要引⼊⼀个依赖,所有的接⼝就会⾃动保护起来!

但是有几个问题可能要解决:

  1. 为什么引⼊ Spring Security 之后没有任何配置所有请求就要认证呢?
  2. 在项⽬中明明没有登录界⾯,登录界⾯怎么来的呢?
  3. 为什么使⽤user控制台密码 能登陆,登录时验证数据源存在哪⾥呢?

二、实现原理

对于之前,如果我们要自己在Spring MVC中实现认证和授权。一般是通过下面
在这里插入图片描述
在对某一个资源进行判断当前用户是否有资格进行访问,那么我们需要在过滤器中处理逻辑操作。如果用户已经认证过并且有某一个权限就放行,否则不放行。
为啥不是拦截器?因为我们需要在访问系统资源之前来处理是否访问的需求
在这里插入图片描述

1、Spring Security的实现原理

1.1、Spring Security 如何完成认证和授权

Spring Security官方网站上有介绍https://docs.spring.io/spring-security/site/docs/5.5.4/reference/html5/#servlet-architecture,
开发者只需要引⼊⼀个依赖,就可以让 Spring Security 对应⽤进⾏保护。Spring Security ⼜是如何做到的呢?

Spring Security认证、授权 等功能都是基于过滤器完成的,从某种意义上来说,代替了我们自己手动在filter实现认证和授权的逻辑判断,交给了Spring Security来做 。
在这里插入图片描述

需要注意的是,默认过滤器并不是直接放在 Web 项⽬的原⽣过滤器链中,⽽是通过⼀个FliterChainProxy 来统⼀管理。Spring Security 中的过滤器链通过FilterChainProxy 嵌⼊到 Web项⽬的原⽣过滤器链中。FilterChainProxy 作为⼀个顶层的管理者,将统⼀管理 Security FilterFilterChainProxy 本身是通过Spring框架提供的 DelegatingFilterProxy 整合到原⽣的过滤器链中。

通俗的理解SpringSeucritySecurity Filter要整合到原生的Filter中,需要借助DelegatingFilterProxy,但是Security Filter不止一个,需要通过FilterChainProxy来管理谁先谁后,但是为了 更加灵活的进行配置,可以定义不同的FilterChain来管理一系列不同的Filter,最后的总体图如下所示:

SecurityFilterChain提供了更加灵活的配置:
在这里插入图片描述

1.2、Security Filters

那么在 Spring Security 中给我们提供那些过滤器? 默认情况下那些过滤器会被加载呢?

在官方网站https://docs.spring.io/spring-security/site/docs/5.5.4/reference/html5/#servlet-delegatingfilterproxy中有说明。

过滤器过滤器作用默认是否加载
ChannelProcessingFilter过滤请求协议 HTTP 、HTTPSNO
WebAsyncManagerIntegrationFilter将 WebAsyncManger 与 SpringSecurity 上下文进行集成YES
SecurityContextPersistenceFilter在处理请求之前,将安全信息加载到 SecurityContextHolder 中YES
HeaderWriterFilter处理头信息加入响应中YES
CorsFilter处理跨域问题NO
CsrfFilter处理 CSRF 攻击YES
LogoutFilter处理注销登录YES
OAuth2AuthorizationRequestRedirectFilter处理 OAuth2 认证重定向NO
Saml2WebSsoAuthenticationRequestFilter处理 SAML 认证NO
X509AuthenticationFilter处理 X509 认证NO
AbstractPreAuthenticatedProcessingFilter处理预认证问题NO
CasAuthenticationFilter处理 CAS 单点登录NO
OAuth2LoginAuthenticationFilter处理 OAuth2 认证NO
Saml2WebSsoAuthenticationFilter处理 SAML 认证NO
UsernamePasswordAuthenticationFilter处理表单登录YES
OpenIDAuthenticationFilter处理 OpenID 认证NO
DefaultLoginPageGeneratingFilter配置默认登录页面YES
DefaultLogoutPageGeneratingFilter配置默认注销页面YES
ConcurrentSessionFilter处理 Session 有效期NO
DigestAuthenticationFilter处理 HTTP 摘要认证NO
BearerTokenAuthenticationFilter处理 OAuth2 认证的 Access TokenNO
BasicAuthenticationFilter处理 HttpBasic 登录YES
RequestCacheAwareFilter处理请求缓存YES
SecurityContextHolder<br />AwareRequestFilter包装原始请求YES
JaasApiIntegrationFilter处理 JAAS 认证NO
RememberMeAuthenticationFilter处理 RememberMe 登录NO
AnonymousAuthenticationFilter配置匿名认证YES
OAuth2AuthorizationCodeGrantFilter处理OAuth2认证中授权码NO
SessionManagementFilter处理 session 并发问题YES
ExceptionTranslationFilter处理认证/授权中的异常YES
FilterSecurityInterceptor处理授权相关YES
SwitchUserFilter处理账户切换NO

可以看出,Spring Security 提供了 30 多个过滤器。默认情况下Spring Boot 在对Spring Security 进⼊⾃动化配置时,会创建⼀个名SpringSecurityFilerChain的过滤器,并注⼊到 Spring 容器中,这个过滤器将负责所有的安全管理,包括⽤户认证、授权、重定向到登录⻚⾯等。具体可以参考WebSecurityConfiguration的源码:
在这里插入图片描述
在这里插入图片描述

2、 Spring Security默认配置和如何自定义配置

从上文中,我们有一个问题就是:为什么引⼊ Spring Security 之后没有任何配置所有请求就要认证呢?
这就不得不提SpringBoot的默认配置类SpringBootWebSecurityConfiguration, 这个类是spring boot⾃动配置类,通过这个源码得知,默认情况下对所有请求进⾏权限控制。
在这里插入图片描述
这就是为什么在引⼊ Spring Security 中没有任何配置情况下,请求会被拦截的原因!
通过上⾯对⾃动配置分析,我们也能看出默认⽣效条件为:
在这里插入图片描述

  • 条件⼀:classpath中存在 SecurityFilterChain.class,
    HttpSecurity.class
  • 条件⼆ 没有⾃定义 WebSecurityConfigurerAdapter.class,
    SecurityFilterChain.class

默认情况下,条件都是满⾜的。WebSecurityConfigurerAdapter 这个类极其重要,
Spring Security 核⼼配置都在这个类中
在这里插入图片描述
如果要对Spring Security进⾏⾃定义配置,就要⾃定义这个类实例,通过覆盖类中⽅法达到修改默认配置的⽬的

三、整个HelloWorld的流程分析

在这里插入图片描述

  1. 请求 /hello 接⼝,在引⼊ spring security 之后会先经过⼀些列过滤器
  2. 在请求到达 FilterSecurityInterceptor时,发现请求并未认证。请求拦截下来,并抛出 AccessDeniedException 异常。
  3. 抛出 AccessDeniedException 的异常会被ExceptionTranslationFilter
    获,这个 Filter 中会调⽤ LoginUrlAuthenticationEntryPoint#commence
    ⽅法给客户端返回 302,要求客户端进⾏重定向到 /login ⻚⾯。
  4. 客户端发送/login请求。
  5. /login请求会再次被拦截器中 DefaultLoginPageGeneratingFilter 拦截到,
    并在拦截器中返回⽣成登录⻚⾯。

就是通过这种⽅式,Spring Security 默认过滤器中⽣成了登录⻚⾯,并返回!

三、HelloWorld中默认⽤户⽣成

HelloWorld中,如何通过默认的用户名和密码对用户进行登录的验证。直接通过源码来进行梳理和说明:
都是在SpringBootWebSecurityConfiguration中设置了默认情况下所有的配置都必须要认证之后才能访问系统。
SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration#defaultSecurityFilterChain方法中对所有的访问都作了限制,要求都需要进行认证。
在这里插入图片描述
那直接打开formLogin方法,发现处理登录的是FormLoginConfigurer类调用了UsernamePasswordAuthenticationFilter这个实例
在这里插入图片描述
查看类中 UsernamePasswordAuthenticationFilter#attempAuthentication得知实际调⽤ AuthenticationManager authenticate ⽅法

这里的filterSpring Security自己定义的方法,处理过滤器的主要逻辑都是在attempAuthentication

在这里插入图片描述
调⽤了 ProviderManager 实现类中AbstractUserDetailsAuthenticationProvider类中⽅法
在这里插入图片描述
直接进入到retrieveUser方法中,发现最终的用户名和密码还是从DaoAuthenticationProvider类中返回的loadUserByUsername来进行比较。
在这里插入图片描述
最后发现在InMemoryUserDetailsManager中返回的user有了用户名和密码
在这里插入图片描述
看到这⾥就知道默认实现是基于 InMemoryUserDetailsManager 这个类,也就是内存的实现!但是点开并没有自己的实现,说明是有自动配置。这个自动配置类就是UserDetailsServiceAutoConfiguration
在这里插入图片描述
以上整体的调用流程:
在这里插入图片描述

三、UserDetailService

我们知道最终要获取系统的用户名和密码的数据都需要靠UserDetails#loadUserByUsername加载出来的用户名密码来获取,但是UserDetailsService是一个接口,有很多实现类在这里插入图片描述
上述分析默认是使用InMemoryUserDetailsManager,通过UserDetailsServiceAutoConfiguration这个自动配置类来完成的,对于UserDetailsServiceAutoConfiguration的源码非常的多,这里只是对关键代码进行梳理。
在这里插入图片描述
从自动配置类的源码中得到,如果是满足一下两个条件,就会自动的将InMemoryUserDetailsManager进行配置。

  1. classpath下存在AuthenticationManager的类
  2. 当系统中没有提供AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.classAuthenticationManagerResolver.class中的任何一个类的实例

默认情况下都会满足以上两个条件,所以Spring Security会默认提供一个InMemoryUserDetailsManager的实例
在这里插入图片描述
这个实例根据SecurityProperties配置类来设置用户信息
在这里插入图片描述
这就是默认⽣成user以及 uuid 密码过程! 另外看明⽩源码之后,就知道只要在配置⽂件中加⼊如下配置可以对内存中⽤户和密码进⾏覆盖。

spring.security.user.name=fckey
spring.security.user.password=admin
spring.security.user.roles=admin,users

所以,如果想要自定义从数据库读取用户名和密码就需要自己定义一个UserDetailsService的子类,这样就不会使用InMemoryUserDetailsManager#loadUserByUsername,而是你自己定义的loadUserByUsername方法。

四、总结

对于Spring Security中的所有认证都是通过AuthenticationManager这个父类来实现的AuthenticationManagerProviderManger、以及 AuthenticationProvider关系,可以对ProviderManager或者是AuthenticationProvider来进行扩展。
在这里插入图片描述
这样子,可以通过AuthenticationProvider来完成多种登录的校验,对于AuthenticationProvider的所有实现类如下图所示:
在这里插入图片描述
还可以通过自定义WebSecurityConfigurerAdapter 扩展 Spring Security 所有默认配置
在这里插入图片描述
UserDetailService ⽤来修改默认认证的数据源信息,后期可以修改为从MyBatis,或者是JDBC来获取。
在这里插入图片描述



http://www.ppmy.cn/news/84029.html

相关文章

为什么要用微服务的分布式架构呢?关于传统架构的分布式集群的猜想!

前提概要&#xff1a; 1、个人对微服务研究不太深入&#xff0c;有些看法可能不太正确。 2、本人很多项目其实都是传统的非微服务架构&#xff0c;而且大部分项目单机硬件&#xff0c;或者云服务器已经能够很好支持。未来三五年内可能都不需要升级扩展。 3、本人很多项目公司规…

解决方案 TestCenter自动测试软件平台

方案概述 TestCenter是一个专为加速您的测试系统软件开发而设计的自动测试系统软件平台&#xff0c;主要应用于测试程序的开发、运行和管理。TestCenter实现了对测试资源管理、测试程序开发与调试、测试数据管理以及测试程序发布等功能的无缝集成和统一部署&#xff0c;这将帮…

打家劫舍 III——力扣337

文章目录 题目描述法一&#xff1a;动态规划 题目描述 法一&#xff1a;动态规划 问题简化&#xff1a;一棵二叉树&#xff0c;树上的每个点都有对应的权值&#xff0c;每个点有两种状态&#xff08;选中和不选中&#xff09;&#xff0c;问在不能同时选中有父子关系的点的情况…

QSocketNotifier:套接字通知程序不能从另一个线程启用或禁用

本文介绍了QSocketNotifier:套接字通知程序不能从另一个线程启用或禁用的处理方法&#xff0c;对大家解决问题具有一定的参考价值&#xff0c;需要的朋友们下面随着小编来一起学习吧&#xff01; 问题描述 限时送ChatGPT账号.. 我尝试构建一个使用QT的多线程游戏服务器&…

【拼多多API 开发系列】百亿补贴商品详情接口,代码封装

为了进行电商平台 PDD 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个 PDD 应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载 PDD API的SDK并掌握基本的API基础知识和调用 4&#xff09;利用SD…

线程的状态,多线程带来的风险,synchronized关键字及死锁问题

目录 状态 线程的意义 多线程带来的风险——线程安全✅ 线程安全的概念 线程不安全的原因 抢占式执行&#xff0c;随机性调度 修改共享数据 原子性->加&#x1f512; 可见性 指令重排序 解决线程不安全问题&#xff08;学完线程再总结&#xff09; synchronized关键字——监…

TPlinker解读

参考&#xff1a; 关系抽取之TPLinker解读加源码分析 TPLinker 实体关系抽取代码解读 实体关系联合抽取&#xff1a;TPlinker TPLinker中文注释版 Tagging TPLinker模型需要对关系三元组(subject, relation, object)进行手动Tagging&#xff0c;过程分为三部分&#xff1a; &…

用工业显微镜来观察生物细胞

尝试用工业显微镜来观察生物细胞&#xff0c;成像质量不是很好&#xff0c;但感觉还是能看一下。专业生物用显微镜真的太贵了。 软件&#xff1a;JCameraPro可以在这下载&#xff1a;www.jfirmware.com 摄像机&#xff1a;OPLENIC Cam的某个OEM版。 OPLENIC Cam很多OEM版&am…