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

文章目录

  • 一、环境搭建
    • 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.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.7.12version>
    parent>

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

 
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

最后写上Controller的测试代码:

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

从HelloWorld深入源码了解SpringSecurity底层逻辑_第1张图片
最后运行项目测试结果得到从HelloWorld深入源码了解SpringSecurity底层逻辑_第2张图片

1.2、整合Spring Security

引入Spring Security相关依赖

  
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

导入成功之后,再次启动项目
从HelloWorld深入源码了解SpringSecurity底层逻辑_第3张图片
启动完之后,控制台会生成一个密码。访问http://localhost:8080/hello,会直接跳到登录页面
从HelloWorld深入源码了解SpringSecurity底层逻辑_第4张图片
默认的用户名是user,密码是控制台中打印的密码,输入之后可以成功进行访问。

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

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

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

二、实现原理

对于没有权限认证的框架之前,如果我们要自己在Spring MVC中实现认证和授权。一般是通过下面这种方式实现
从HelloWorld深入源码了解SpringSecurity底层逻辑_第5张图片
在对某一个资源做判断,判断当前用户是否有资格访问该资源,很容易想到需要在过滤器中处理。
为啥不是拦截器?因为我们需要在访问系统资源之前来处理是否访问的需求
从HelloWorld深入源码了解SpringSecurity底层逻辑_第6张图片

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提供了更加灵活的配置:
从HelloWorld深入源码了解SpringSecurity底层逻辑_第7张图片

1.2、Security Filters

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

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

过滤器 过滤器作用 默认是否加载
ChannelProcessingFilter 过滤请求协议 HTTP 、HTTPS NO
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 Token NO
BasicAuthenticationFilter 处理 HttpBasic 登录 YES
RequestCacheAwareFilter 处理请求缓存 YES
SecurityContextHolder
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的源码:
从HelloWorld深入源码了解SpringSecurity底层逻辑_第8张图片
从HelloWorld深入源码了解SpringSecurity底层逻辑_第9张图片

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

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

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

默认情况下,条件都是满⾜的。WebSecurityConfigurerAdapter 这个类极其重要,
Spring Security 核⼼配置都在这个类中
从HelloWorld深入源码了解SpringSecurity底层逻辑_第12张图片
如果要对Spring Security进⾏⾃定义配置,就要⾃定义这个类实例,通过覆盖类中⽅法达到修改默认配置的⽬的

三、整个HelloWorld的流程分析

从HelloWorld深入源码了解SpringSecurity底层逻辑_第13张图片

  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方法中对所有的访问都作了限制,要求都需要进行认证。
从HelloWorld深入源码了解SpringSecurity底层逻辑_第14张图片
那直接打开formLogin方法,发现处理登录的是FormLoginConfigurer类调用了UsernamePasswordAuthenticationFilter这个实例
从HelloWorld深入源码了解SpringSecurity底层逻辑_第15张图片
查看类中 UsernamePasswordAuthenticationFilter#attempAuthentication得知实际调⽤ AuthenticationManager authenticate ⽅法

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

从HelloWorld深入源码了解SpringSecurity底层逻辑_第16张图片
调⽤了 ProviderManager 实现类中AbstractUserDetailsAuthenticationProvider类中⽅法
从HelloWorld深入源码了解SpringSecurity底层逻辑_第17张图片
直接进入到retrieveUser方法中,发现最终的用户名和密码还是从DaoAuthenticationProvider类中返回的loadUserByUsername来进行比较。
从HelloWorld深入源码了解SpringSecurity底层逻辑_第18张图片
最后发现在InMemoryUserDetailsManager中返回的user有了用户名和密码
从HelloWorld深入源码了解SpringSecurity底层逻辑_第19张图片
看到这⾥就知道默认实现是基于 InMemoryUserDetailsManager 这个类,也就是内存的实现!但是点开并没有自己的实现,说明是有自动配置。这个自动配置类就是UserDetailsServiceAutoConfiguration
从HelloWorld深入源码了解SpringSecurity底层逻辑_第20张图片
以上整体的调用流程:
从HelloWorld深入源码了解SpringSecurity底层逻辑_第21张图片

三、UserDetailService

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

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

默认情况下都会满足以上两个条件,所以Spring Security会默认提供一个InMemoryUserDetailsManager的实例
从HelloWorld深入源码了解SpringSecurity底层逻辑_第24张图片
这个实例根据SecurityProperties配置类来设置用户信息
从HelloWorld深入源码了解SpringSecurity底层逻辑_第25张图片
这就是默认⽣成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来进行扩展。
从HelloWorld深入源码了解SpringSecurity底层逻辑_第26张图片
这样子,可以通过AuthenticationProvider来完成多种登录的校验,对于AuthenticationProvider的所有实现类如下图所示:
从HelloWorld深入源码了解SpringSecurity底层逻辑_第27张图片
还可以通过自定义WebSecurityConfigurerAdapter 扩展 Spring Security 所有默认配置
从HelloWorld深入源码了解SpringSecurity底层逻辑_第28张图片
UserDetailService ⽤来修改默认认证的数据源信息,后期可以修改为从MyBatis,或者是JDBC来获取。
从HelloWorld深入源码了解SpringSecurity底层逻辑_第29张图片


你可能感兴趣的:(spring,java,Spring,Security,源码,spring,boot)