2. 环境搭建
2.1 环境搭建
新建一个名为SpringSecurity的空项目,在里面新建module,创建名为spring-security-01的Spring Initializr、Spring Web的SpringBoot项目。
-
IDEA添加自动导包、格式化配置
-
新建HelloController用于测试
package com.study.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName HelloController
* @Description TODO
* @Date 2022/7/4 17:51
* @Version 1.0
*/
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/hello")
public String hello() {
System.out.println("Hello Spring Security");
return "hello spring security";
}
}
- 启动项目进行测试,访问路径:http://localhost:8080/hello/hello
同时,IDEA控制台输出:Hello Spring Security,表示项目创建成功!
2.2 整合Spring Security
1.引入spring security相关依赖,刷新maven
<!--引入spring security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.再次启动项目:
控制台输出
Using generated security password: 5f37fc93-1ad0-4333-a84b-cec8dc25c16c
其中,5f37fc93-1ad0-4333-a84b-cec8dc25c16c
为引入spring security后生成的登录密码
3.重新访问:http://localhost:8080/hello/hello
发现会跳出登录页,此时需要输入用户名和密码,默认用户名为user,密码为启动项目后IDEA控制台输出的密码(Using generated security password),即生成的UUID
输入用户名和密码后,跳转到之前的测试页面
能实现上述结果,表示SpringBoot整合SpringSecurity环境搭建成功!
注意:以后每次重新启动项目后都会生成新的登录密码,登录用户名始终默认为user,以后的任何请求都要经过此登录页面后才能正常访问!!!
这就是 Spring Security 的强大之处,只需要引入一个依赖,所有的接口就会自动保护起来!
思考🤔?
- 为什么引入 Spring Security 之后没有任何配置所有请求就要认证呢?
- 在项目中明明没有登录界面,登录界面怎么来的呢?
- 为什么使用 user 和 控制台密码 能登陆,登录时验证数据源存在哪里呢?
2.3实现原理
在 Spring Security 中 认证、授权 等功能都是基于过滤器完成的
。
现有的SringBoot默认集成了tomcat,springsecurity中的filter是通过spring框架中的DelegatingFilterProxy类整合到java web原生的filter
。如果javaweb没有原生的filter,DelegatingFilterProxy会把请求转发到spring的filter中,而springsecurity的filter属于spring filter中的一部分,只有整合到原生的请求中才具有拦截请求的能力,不整合是没有拦截请求的能力。
DelegatingFilterProxy的作用是将Servlet容器中的请求转发给一个Spring的bean,,该bean实际上是一个过滤器链。这个过滤器链由Spring Security配置定义,它包含了一系列的过滤器,用于实现身份验证、授权、会话管理等安全相关的功能。
需要注意的是,默认过滤器并不是直接放在 Web 项目的原生过滤器链中,而是通过一个 FlterChainProxy 来统一管理。Spring Security 中的过滤器链通过 FilterChainProxy 嵌入到 Web项目的原生过滤器链中。FilterChainProxy作为一个顶层的管理者,将统一管理 Security Filter。FilterChainProxy 本身是通过 Spring 框架提供的 DelegatingFilterProxy 整合到原生的过滤器链中。
而且还可以实现多组过滤器,针对不同请求,使用不同组的过滤器链
总结:请求一般都是通过Filter进行拦截的。现有的SpringBoot项目中已经默认集成了Tomcat,Spring Security底层使用了大量的Filter来实现权限管理,但只有JavaWeb原生的Filter能在受限资源之前拿到JavaWeb请求,而Spring Security中的Filter不能直接对请求进行直接拦截,需要通过Spring框架中集成的DelegatingFilterProxy将Spring Security中的Filter整合到JavaWeb原生的Filter上,从而实现对请求的拦截。
2.4 Security Filters
请求会根据表格的顺序从上到下经过过滤器
过滤器 | 过滤器作用 | 默认是否加载 |
---|---|---|
ChannelProcessingFilter | 过滤请求协议 HTTP 、HTTPS | NO |
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 的过滤器,使用这个过滤器管理一系列filter,并注入到 Spring 容器的DelegatingFilterProxy中,这个过滤器将负责所有的安全管理,包括用户认证、授权、重定向到登录页面等。
具体可以参考WebSecurityConfiguration的源码:
获取过滤器
默认加载的15个Filter的执行顺序如下:
2.5 SpringBoot对SpringSecurity的默认配置
当引入SpringSecurity依赖以后,SpringBoot会自动对springsecurity进行自动化配置,并创建一个servlet filter 叫做SpringSecurityFilterChain
SpringBootWebSecurityConfiguration这个类是 spring boot自动配置类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http.authorizeRequests().anyRequest()
.authenticated().and().formLogin().and().httpBasic();
return http.build();
}
}
@ConditionalOnWebApplication(type = Type.SERVLET)是说当前运行的容器时servlet时当前配置生效(SpringBoot默认集成的tomcat,而tomcat就是一个servlet)
@ConditionalOnDefaultWebSecurity的条件要求是DefaultWebSecurityCondition
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
static class Classes {
}
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
static class Beans {
}
}
-
条件一:classpath中存在 SecurityFilterChain.class、HttpSecurity.class两个类
-
条件二:没有自定义的WebSecurityConfigurerAdapter.class(主要用于自定义配置)、SecurityFilterChain.class(主要用于自定义Filter)两个类的实例(我们并没有继承这两个类自定义配置,并注入容器)
默认情况下,条件都是满足的。WebSecurityConfigurerAdapter 这个类极其重要,Spring Security 核心配置都在这个类中:
如果要对 Spring Security 进行自定义配置,就要自定义这个类实例,我们继承这个类然后重写里面的方法,然后就有了WebSecurityConfigurerAdapter的实例,原有的默认配置失效(条件二不符合)通过覆盖类中方法达到修改默认配置的目的。
一般继承WebSecurityConfigurerAdapter重写配置,继承SecurityFilterChain重写扩展filter
其中的config方法被经常使用:
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http.authorizeRequests((requests) -> {
((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)requests.anyRequest()).authenticated();
});
http.formLogin();
http.httpBasic();
}