文章目录
- 一、环境搭建
- 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
来获取。