一、简单导入依赖
1、导入pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springsecurity-simple-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<!-- web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringSecurity依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2、创建controller测试
@SpringBootApplication
public class SpringSecuritySimpApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecuritySimpApplication.class,args);
}
}
@RestController
public class HelloController {
@RequestMapping("hello")
public String hello() {
return "hello world";
}
}
3、简单导入后,接口资源就受到保护了,是什么原理,以下引申出三个问题
1、如何在只是导入了一个springsecurity-statar 所有的接口都进行保护了;
2、登录页面的这个html 是从何而来,我们自己项目也没有写html怎么就出来了;
3、登录页的这个user 以及默认的用户名和密码是怎么来的;
二、问题1:只是导入了一个springsecurity-statar 依赖,为何 所有的接口都进行保护了
2.1 DelegatingFilterProxy
DelegatingFilterProxy 作用是打通servlet 和spring 通讯的桥梁,介入servilet 容器和spirng 应用;我们在DelegatingFilterProxy 基础上使用spring提供的过滤器都被该过滤器所代理;
2.2 FilterChainProxy 、
FilterChainProxy 为过滤器链,允许委托其他多个过滤器,底层实现,框架做好了,不需要我们处理;
2.3 SecurityFilterChain
SecurityFilterChain 我们自己配置使用的过滤器链,提供我们自定义配置;该类是我们接触到的,最终也是要配置该过滤器来实现一系列的验证
2.4 SpringBootWebSecurityConfiguration
SpringBootWebSecurityConfiguration 每一个starter都有一个autoconfig入口配置,该类是入口,从该类开始出发,追求答案
2.4.1 注解 @ConditionalOnDefaultWebSecurity
进入后,导入了一个 DefaultWebSecurityCondition 的类
2.4.1.1 源码
2.4.2 @Conditional(DefaultWebSecurityCondition.class)
里面有个conditon注解@Conditional(DefaultWebSecurityCondition.class),DefaultWebSecurityCondition进去后看到里面有两个方法;
条件1:@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class }) 判断是否有class文件,SecurityFilterChain 或者HttpSecurity,只要导入了,肯定会有这两个class的文件;即使我们不配置;
条件2:@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class }) 找不到对应的bean,注意这个是bean,而不是class,因我们创建的时候,没有重写或者继承WebSecurityConfigurerAdapter 所以是找不到的;至此该注解条件生效;
2.4.2.1 源码截图
2.4.3 WebSecurityConfigurerAdapter
该类作用:后续我们可以继承该类,用于重写里面方法,配置自己的验证规则;
跟随代码 @ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class) 进到该类后,找到 configure(HttpSecurity http) 方法,是不是一下子豁然开朗了;看到了我们熟悉的配置;
这个配置是不是很熟悉了: http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
所有的请求都需要认证;至此,导入依赖自动加了认证;spring官方也推荐我们重写该方法配置;
三、问题2:登录页面的这个html 是从何而来
是不是有疑问,这个页面我自己项目也没写,怎么就到这个form表单页面了;
3.1 FormLoginConfigurer
FormLoginConfigurer中的init() 可以得知,初始化会调用 ,该方法做了什么,是不是初始化了一个DefaultLoginPageGeneratingFilter 的过来器
3.1.1 源码截图
3.2 流程分析
1. 请求/hello 接口,在引入 spring security 之后会先经过一些列过滤器
2. 在请求到达 FilterSecuritylnterceptor时,发现请求并未认证。请求拦截下来,并抛出
AccessDeniedException 异常。
抛出AccessDeniedExceptsn 的异常会被 ExceptionTranslationFilter 捕获,这个Filter 中
会调用 LoginUrlAuthenticationEntryPoint#commence 方法给客户端返回 302,要求客户
端进行重定向到 /login 页面。
4. 客户端发送/login 请求。
5. /login 请求会再次被拦截器中 DefaultLoginPageGeneratingFilter 拦截到,并在拦截器中返回生成登录页面。
3.3 DefaultLoginPageGeneratingFilter
DefaultLoginPageGeneratingFilter 默认的redirect页面,至此自动生成的登录页面是通过浏览器生成一个表单写出的;
3.3.1 源码截图
四、问题3:默认的用户名和密码是怎么来的
是不是疑问为什么控制台能打印出密码呢,怎么实现的;
4.1 http.formLogin()方法
用来初始化表单一些配置,如用户名 密码 等;
4.1.1 源码
4.2 FormLoginConfigurer 新建
4.3 UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter 通过构造方法里面new了一个token过滤器,查看attemptAuthentication 方法
4.4 断点调试 ProviderManager
通过 Authentication 参数可以知道传过来的是 一个UsernamePasswordAuthenticationToken 对象;
4.5 DaoAuthenticationProvider 最终实现类
4.5.1 InMemoryUserDetailsManager
跟踪后得知,UserDetailsService的实现类是InMemoryUserDetailsManager,最终从内存中获取到用户信息,然后得到密码;
4.6 InMemoryUserDetailsManager 基于内存的用户会生效
我们也没有配置这个基于内存的用户信息,为什么自动生成用户和密码了
4.6.1 UserDetailsServiceAutoConfiguration
一般默认配置都是有一个starter 启动器
4.6.2 @ConditionalOnClass(AuthenticationManager.class)
由注解得知,@ConditionalOnClass(AuthenticationManager.class)
当类中有AuthenticationManager,导入依赖后,肯定会有;
@ConditionalOnMissingBean(
value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
AuthenticationManagerResolver.class };当 bean中没有 AuthenticationManager并且UserDetailsService并且AuthenticationProvider 时候,该自动配置InMemoryUserDetailsManager 类就生效,并且注入为bean;
所以说,只要我们重新定义了这三个其中一个设置为bean,那么基于InMemoryUserDetailsManager 这个就不生效了,这就是为什么我们实现了UserDetailsService后,就没有内存认证的这个类了;
4.6.3 SecurityProperties
我们看到 inMemoryUserDetailsManager中传入了一个 SecurityProperties 该类就是一个
@ConfigurationProperties 标记的注解,也就是说 我们在配置文件写上 spring.security. username 是不是就可以映射到属性里面了
通过该类 User user = properties.getUser(); 调用了该方法,可以看到 User对象里面这个name就是叫user,密码为随机生成的uuid,这就是为什么默认的登录名为user
4.6.4 自定义登录时候的用户名和密码
当我们在配置文件中自定义了用户名密码后,随机密码和用户名就为自定义的了,至此 user 和密码整个流程就出来了