文章目录
- 一、环境搭建
- 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 的强⼤之处,只需要引⼊⼀个依赖,所有的接⼝就会⾃动保护起来!
但是有几个问题可能要解决:
- 为什么引⼊
Spring Security之后没有任何配置所有请求就要认证呢? - 在项⽬中明明没有登录界⾯,登录界⾯怎么来的呢?
- 为什么使⽤
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 Filter。FilterChainProxy 本身是通过Spring框架提供的 DelegatingFilterProxy 整合到原⽣的过滤器链中。
通俗的理解:SpringSeucrity的Security 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 、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<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的流程分析

- 请求
/hello接⼝,在引⼊spring security之后会先经过⼀些列过滤器 - 在请求到达
FilterSecurityInterceptor时,发现请求并未认证。请求拦截下来,并抛出AccessDeniedException异常。 - 抛出
AccessDeniedException的异常会被ExceptionTranslationFilter捕
获,这个Filter中会调⽤LoginUrlAuthenticationEntryPoint#commence
⽅法给客户端返回302,要求客户端进⾏重定向到/login⻚⾯。 - 客户端发送
/login请求。 /login请求会再次被拦截器中DefaultLoginPageGeneratingFilter拦截到,
并在拦截器中返回⽣成登录⻚⾯。
就是通过这种⽅式,Spring Security 默认过滤器中⽣成了登录⻚⾯,并返回!
三、HelloWorld中默认⽤户⽣成
在HelloWorld中,如何通过默认的用户名和密码对用户进行登录的验证。直接通过源码来进行梳理和说明:
都是在SpringBootWebSecurityConfiguration中设置了默认情况下所有的配置都必须要认证之后才能访问系统。
在SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration#defaultSecurityFilterChain方法中对所有的访问都作了限制,要求都需要进行认证。

那直接打开formLogin方法,发现处理登录的是FormLoginConfigurer类调用了UsernamePasswordAuthenticationFilter这个实例

查看类中 UsernamePasswordAuthenticationFilter#attempAuthentication得知实际调⽤ AuthenticationManager 中 authenticate ⽅法
这里的
filter是Spring Security自己定义的方法,处理过滤器的主要逻辑都是在attempAuthentication中

调⽤了 ProviderManager 实现类中AbstractUserDetailsAuthenticationProvider类中⽅法

直接进入到retrieveUser方法中,发现最终的用户名和密码还是从DaoAuthenticationProvider类中返回的loadUserByUsername来进行比较。

最后发现在InMemoryUserDetailsManager中返回的user有了用户名和密码

看到这⾥就知道默认实现是基于 InMemoryUserDetailsManager 这个类,也就是内存的实现!但是点开并没有自己的实现,说明是有自动配置。这个自动配置类就是UserDetailsServiceAutoConfiguration

以上整体的调用流程:

三、UserDetailService
我们知道最终要获取系统的用户名和密码的数据都需要靠UserDetails#loadUserByUsername加载出来的用户名密码来获取,但是UserDetailsService是一个接口,有很多实现类
上述分析默认是使用InMemoryUserDetailsManager,通过UserDetailsServiceAutoConfiguration这个自动配置类来完成的,对于UserDetailsServiceAutoConfiguration的源码非常的多,这里只是对关键代码进行梳理。

从自动配置类的源码中得到,如果是满足一下两个条件,就会自动的将InMemoryUserDetailsManager进行配置。
- 在
classpath下存在AuthenticationManager的类 - 当系统中没有提供
AuthenticationManager.class,AuthenticationProvider.class,UserDetailsService.class,AuthenticationManagerResolver.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这个父类来实现的AuthenticationManager、ProviderManger、以及 AuthenticationProvider关系,可以对ProviderManager或者是AuthenticationProvider来进行扩展。

这样子,可以通过AuthenticationProvider来完成多种登录的校验,对于AuthenticationProvider的所有实现类如下图所示:

还可以通过自定义WebSecurityConfigurerAdapter 扩展 Spring Security 所有默认配置

UserDetailService ⽤来修改默认认证的数据源信息,后期可以修改为从MyBatis,或者是JDBC来获取。




















