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


对于任何的项目都需要从一个案例来分析出其中技术的门道。 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来获取。
在这里插入图片描述


  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值