在 Spring Security 中只需要引入一个依赖,所有的接口就会自动保护起来!
原理在 Spring Security 中 认证、授权 等功能都是基于过滤器完成的
默认用户生成- 查看 SpringBootWebSecurity Configuration#defaultSecurity FilterChain 方法表单登录
- 处理登录为 FormLoginConfigurer 类中 调用 UsernamePasswordAuthenticationFilter这个类实例
- 查看类中 UsernamePasswordAuthenticationFilter#attempAuthentication 方法得知实际调用 AuthenticationManager 中 authenticate 方法
- 调用 ProviderManager 类中方法 authenticate
- 调用了 ProviderManager 实现类中 AbstractUserDetailsAuthenticationprovider类中方法
- 最终调用实现类 DaoAuthenticationProvider 类中方法比较. 看到这里就知道默认实现是基于 InMemoryUserDetailsManager 这个类, 也就是内存的实现.
通过刚才源码分析也能得知 UserDetailService 是顶层父接口,接口中 loaduserByName 方法是用来在认证时进行用户名认证方法.默认实现使用是内存实现,如果想要修改数据库实现我们只需要自定义 UserDetailservice 实现, 最终返回 UserDetails 实例即可.
UserDetailServiceAutoConfigutation- 从自动配置源码中得知当classpath 下存在 AuthenticationManager 类
- 当前项目中,系统没有提供 AuthenticationManager.class、Authenticationbrovidarcaccserlptaisherviceclass、AuthenticationManagerResolver.class, 实例.
- 默认情况下都会满足,此时Spring Security会提供一个 InMemoryUserDetailManager 实例
其实根据 SecurityAutoConfiguration 类上的注解 @EnableConfigurationProperties(SecurityProperties.class) 找到对应的SecurityProperties 类就可以找到了该配置.
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
private final User user = new User();
public User getUser() {
return this.user;
}
public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
这就是默认生成 user 以及 uuid 密码过程!另外看明白源码之后,就知道只要在配置文件中加入如下配置可以对内存中用户和密码进行覆盖
spring:
security:
user:
name: root
password: root
三大概念
- Authentication 相关
![[Pasted image 20220506215404.png]]
- WebSecurityConfigurerAdaptor 扩展 Spring Security 所有默认配置
- UserDetailService 用来修改默认认证的数据源信息 (只有一个方法#loadUserByUsername)
WebSecurity 配置的是公共资源, HttpSecurity 配置的是 http 请求
- authorizeRequests() 代表开启请求的权限控制
- mvcMatchers() 代表匹配请求 (注意参数都需要斜杠 / )
- permitAll() 放行资源 (放在认证前面)
- anyRequest().authenticated() 代表所有请求, 必须认证后才能访问
- formLogin() 代表开启表单验证
- and() 返回 HttpSecurity
使用:
// 放行资源要写在【任何】前面
.anyRequest().authenticated()
.and() // 特性: 匿名内部类中使用 类名.this.属性名,调用外部类属性 e.g.// ExpressionUrlAuthorizationConfigurer.this.and();
.formLogin()
// 登陆操作 在 formLogin() 后面,对登陆进行个性化设置
.loginPage("/login.html") // 指定登陆页面,一旦定义必须指定登陆api
.loginProcessingUrl("/doLogin") // 指定登陆api ,必须同时指定登陆页面
.usernameParameter("uname") // 修改默认用户名参数
.passwordParameter("pwd") // 修改默认密码参数
// .successForwardUrl("/hello") // forward 转发,url不变 (只能二选一)
// .defaultSuccessUrl("/hello") // redirect 重定向,url改变(只能二选一)
// .defaultSuccessUrl("/index", true) // default的特性,如果之前访问受限资源,会优先上一次。需要设为true才能强转
.successHandler(new LoginSuccessHandler())
// .failureForwardUrl("/login.html") // 转发 {request} 作用域中拿
// .failureUrl("/login.html") // 重定向(sendRedirect) {session} 作用域中拿
.failureHandler(new LoginFailureHandler())
.and()
// 登出操作 在 HttpSecurity 类中, 前面需要加 and()
.logout()
// .logoutUrl("/logout") // 默认的
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/aa", "GET"), // 注销url
new AntPathRequestMatcher("/bb", "POST")
))
.invalidateHttpSession(true) // 默认为true 回话失效
.clearAuthentication(true) // 默认为true 清楚认证标记
.logoutSuccessUrl("/login.html") // 注销成功跳转页面
// 前后端分离的项目, 只需要返回一个json格式消息提示就行
// logoutSuccessHandler(new MyLogoutSuccessHandler()) // 调用LogoutSuccessHandler#onLogoutSuccess 方法
// 禁止 csrf 跨站请求保护
.and().csrf().disable();
登陆用户数据获取
SecurityContextHolder
Spring security 会将登录用户数据保存在 Session 中。但是,为了使用方便 Spring Security 在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。
SecurityContextHolder 中的数据保存默认景通过Threadlocal 来实现的,使用 Threadlocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security 会将 Security ContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到SecuritvContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将 Security SecurityContextHolder 中的数据清空,实际上 SecurityContextHolder 中存储是 SecurityContext. 在 SecurityContext 中存储是 Authentication.
SecurityContextHolder > SecurityContext > AuthenticationSecurityContextHolder 中的 strategy 是典型的策路设计模式
- MODE_THREADLOCAL :这种存放策略是将 Securitycontext 存放在 ThreadLocal中,大家知道 Threadlocal 的特点是在哪个线程中存储就要在哪个线程中读取,这其实非常适合web 应用,因为在默认情况下,一个请求无论经过多少Filter 到达 Servlet, 都是由一个线程来处理的。这也是 Security ContextHolder 的默认存储策略,这种存储策略意味着如果在具体的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到。
- MODE_INHERITABLETHREADLOCAL:这种存储模式适用于多线程环境,如果希望在子线程中也能够获取到登录用户数据,那么可以使用这种存储模式。(需要设置运行参数 -Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL 来开启)
- MODE GLOBAL:这种存储模式实际上是将数据保存在一个静态变量中,在Javaweb开发中,这种模式很少使用到。
通过 SecurityContextHolder 可以得知, SecurityContextHolderStrategy 接口用来定义存储策略方法
接口中一共定义了四个方法:
- clearcontext:该方法用来清除存储的 SecurityContex对象
- getcontext:该方法用来获取存储的 Securitvcontext 对象
- setcontext:该方法用来设置存储的 Securitvcontext 对象
- create Empty Context :该方法则用来创建一个空的 SecuritvContext 对象.



