Spring Security
文章目录
什么是 Spring Security
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring Security 的核心组件
本节介绍Spring Security在Servlet身份验证中使用的主要架构组件。如果需要解释这些部分如何组合在一起的具体流程,请查看特定于身份验证机制的部分。
- SecurityContextHolder - SecurityContextHolder是Spring Security存储被身份验证者的详细信息的地方。
- SecurityContext - 从 SecurityContextHolder 获取,包含当前经过身份验证的用户的身份验证。
- Authentication - 可以是 AuthenticationManager 的输入,以提供用户提供的用于身份验证的凭据(Token),也可以是 SecurityContext 中的当前用户。
- GrantedAuthority - 授予身份验证主体的权限(即角色、作用域等)
- AuthenticationManager - 定义Spring Security过滤器如何执行身份验证的API。
- ProviderManager - AuthenticationManager 最常见的实现。
- AuthenticationProvider - 提供程序管理器用于执行特定类型的身份验证。
- Request Credentials with AuthenticationEntryPoint - 用于从客户端请求凭据(即重定向到登录页面、发送 WWW 身份验证响应等)
- AbstractAuthenticationProcessingFilter - 用于身份验证的基本过滤器。这也很好地了解了身份验证的高级流程以及各个部分如何协同工作。
关系的简要示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sfrsO9wC-1654844540950)(https://app.yinxiang.com/FileSharing.action?hash=1/c73daea197b506c37fd4b8a220689411-31431)]
SecurityContextHolder 示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JIauoIE6-1654844540952)(https://app.yinxiang.com/FileSharing.action?hash=1/e5736a02d5ccb739ae0bc4698fa58f48-22163)]
Spring Security 的加密工具
- BCryptPasswordEncoder(bcrypt 算法加密)
- Argon2PasswordEncoder(argon2 算法加密)
- Pbkdf2PasswordEncoder(pbkdf2 算法加密)
- SCryptPasswordEncoder(scrypt 算法加密)
Spring Security 的架构
这是 WebSecurityConfigurerAdapter 的初始化加载流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6FcKHhY-1654844540952)(https://app.yinxiang.com/FileSharing.action?hash=1/ad820c1970141c44e14862330e49d6d5-73403)]
从上侧流程图我们可以大致了解 Spring Security 的架构应该如下
其中 FilterChain 中包含各种各样的 filter
登陆成功流程大致如下图
-
当用户提交他们的凭据时,
AbstractAuthenticationProcessingFilter
将从被验证的HttpServletRequest
处创建一个Authentication
。Authentication
类型的创建工作则依赖于
AbstractAuthenticationProcessingFilter
的子类。例如
UsernamePasswordAuthenticationFilter
从已提交的HttpServletRequest
中获取用户名密码并创建
UsernamePasswordAuthenticationToken
-
接下来,将
Authentication
传递给AuthenticationManager
进行身份验证。 -
如果身份验证失败,
- 清除
SecurityContextHolder
- 调用
RememberMeServices
.loginFail
。如果没有配置 remeberme ,则不会进行任何操作 - 调用
AuthenticationFailureHandler
- 清除
-
如果身份验证成功,则Success。
SessionAuthenticationStrategy
收到新登录通知。SecurityContextHolder
设置Authentication
。稍后SecurityContextPersistenceFilter
将SecurityContext
保存到HttpSession
中.- 调用
RememberMeServices
.loginSuccess
。如果没有配置 remeberme ,则不会进行任何操作 ApplicationEventPublisher
发布InteractiveAuthenticationSuccessEvent
事件- 调用
AuthenticationSuccessHandler
。
Spring Security 的使用
引入 Spring Security
pom.xml
引入 Spring Security 时我们需要在 pom.xml
中添加
<!--Spring Security 包引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Spring Boot Web 包引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring Boot 测试包引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Spring Security 测试包引入-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
application.yml
在 application.yml 中编写配置文件
server:
port: 8088 # 服务器端口
配置
WebSecurityConfigurerAdapter
类
// Step1: 创建自己的 WebSecurityConfigurerAdapter
@Configuration
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
// Step2: 重写 configure 方法
@Override
public void configure(HttpSecurity http){
}
}
添加密码加密器
配置类
WebSecurityConfigurerAdapter
// 注入密码加密器
@Bean
PasswordEncoder passwordEncoder(){
// return new BCryptPasswordEncoder();
// return new Argon2PasswordEncoder();
// return new SCryptPasswordEncoder();
return new Pbkdf2PasswordEncoder();
}
使用加密器对密码进行加密以及密码之间的匹配
String password = "RawPassword";
String pwdCrypt = passwordEncoder.encode(password);
passwordEncoder.matches(password,pwdCrypt);
配置安全策略
登陆成功的处理与配置
配置
WebSecurityConfigurerAdapter
类
@Override
public void configure(HttpSecurity http){
try {
http.formLogin()
.usernameParameter("usr")// 设定登陆时用户名的参数名,默认为 username
.passwordParameter("pwd")// 设定登陆时密码的参数名,默认为 password
.loginPage("login.html") // 设定登陆页面
.loginProcessingUrl("/login") // 处理登陆的 url
.successForwardUrl("/loginSuccess") // 登陆成功后跳转的 url
.successHandler(new AuthenticationSuccessHandler() { // 登陆成功的处理器
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
}
}).failureForwardUrl("/loginFailure") // 失败跳转的 url
.failureHandler(new AuthenticationFailureHandler() { // 失败的请求处理器
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
通过权限控制访问
配置
WebSecurityConfigurerAdapter
类
@Override
public void configure(HttpSecurity http){
try {
http.authorizeRequests()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/status").permitAll() // 允许所有请求通过访问
.regexMatchers("*.png").permitAll() // 通过正则表达式进行匹配
.antMatchers("/userResource").hasAuthority("USER") // 具有某个权限后方可访问
.antMatchers("/userResource1").hasAnyAuthority("SYS","USER") // 具有任意一个权限后方可访问
.antMatchers("/roleResource").hasRole("role1") // 具有某个角色后方可访问,角色的权限名称为 “ROLE_” 前缀加上角色名称
.antMatchers("/roleResource1").hasAnyRole("role1","role2")// 具有任意一个角色后方可访问
.antMatchers("/accessAuthority").access("hasAnyAuthority(SYS,USER)")// 具有任意一个权限后方可访问
.antMatchers("/accessRoles").access("hasAnyRole(role1,role2)")// 具有任意一个角色后方可访问
.anyRequest().authenticated(); // 其余任何请求都需要经过访问
} catch (Exception e) {
e.printStackTrace();
}
}
进行 Token 配置
配置
WebSecurityConfigurerAdapter
类
@Override
public void configure(HttpSecurity http){
try {
http.rememberMe().rememberMeParameter("remeberme")
.tokenRepository(new InMemoryTokenRepositoryImpl()) // 设置 Token 仓库,用于存储,更新,和获得 Token
.userDetailsService(username -> // 配置验证时的 UserDetailService
new User(username,"pwd", AuthorityUtils.commaSeparatedStringToAuthorityList("sys")))
.tokenValiditySeconds(1800);//token 有效时间
} catch (Exception e) {
e.printStackTrace();
}
}
使用自定义的 UserDetailsService
UserDetailsService
接口
创建 MyUserDetailsService
类并且实现 UserDetailsService
接口
@Service
public class MyUserDetailsService implements UserDetailsService{
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = "pwd";
String pwdCrypt = passwordEncoder.encode(password);// 模拟从数据中取出密码
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority("ROLE_user"));
// 这里只是模拟,实际可以与数据库进行交互获得用户权限
UserDetails user = new User(username,pwdCrypt,authorityList);
return user;
}
}
配置
WebSecurityConfigurerAdapter
类
// 注入创建的 UserService
@Bean
public MyUserDetailsService myUserDetailsService(){
return new MyUserDetailsService();
}
// 重写 Configure 方法,使配置生效
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService()) // 注入 UserDetailsService
.passwordEncoder(passwordEncoder());
}
// 将代理 AuthenticationManager 注册成 Bean,供直接使用
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Ps:
一般来说UserDetails
一般是由UserDetailsService
来返回的。DaoAuthenticationProvider
验证UserDetails
并返回一个Authentication
对象
使用自定义的 AuthenticationEntryPoint
AuthenticationEntryPoint
他所建模的概念是:“认证入口点”。它在用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程。
这为登陆失败时的流程:
- 首先,
ExceptionTranslationFilter
调用FilterChain.doFilter(request, response)
来调用应用程序的其余部分 - 如果用户未通过身份验证或它是一个AuthenticationException,则开始身份验证。
- 清除 SecurityContextHolder
- HttpServletRequest保存在RequestCache. 当用户成功认证后,RequestCache用于重放原始请求。
- AuthenticationEntryPoint用于从客户端请求凭据。例如,它可能会重定向到登录页面或发送WWW-Authenticate标头。
- 否则,如果是AccessDeniedException,则拒绝访问。被AccessDeniedHandler调用来处理拒绝访问。
AuthenticationEntryPoint
接口
创建一个 MyAuthenticationEntryPoint
类,并实现 AuthenticationEntryPoint
接口
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); // 自定义处理返回信息
}
}
配置
WebSecurityConfigurerAdapter
类
@Autowired
MyAuthenticationEntryPoint authenticationEntryPoint; // 注入自己实现的 EntryPoint
// Step2: 重写 configure 方法
@Override
public void configure(HttpSecurity http){
try {
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
} catch (Exception e) {
e.printStackTrace();
}
}
Spring Security 自带的 AuthenticationEntryPoint
-
Http403ForbiddenEntryPoint
设置响应状态字为403,并非触发一个真正的认证流程。通常在一个预验证(pre-authenticated authentication)已经得出结论需要拒绝用户请求的情况被用于拒绝用户请求。 -
HttpStatusEntryPoint
设置特定的响应状态字,并非触发一个真正的认证流程。 -
LoginUrlAuthenticationEntryPoint
根据配置计算出登录页面url,将用户重定向到该登录页面从而开始一个认证流程。 -
BasicAuthenticationEntryPoint
对应标准Http Basic认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Basic realm="xxx"触发标准Http Basic认证流程。 -
DigestAuthenticationEntryPoint
对应标准Http Digest认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Digest realm="xxx"触发标准Http Digest认证流程。 -
DelegatingAuthenticationEntryPoint
这是一个代理,将认证任务委托给所代理的多个AuthenticationEntryPoint对象,其中一个被标记为缺省AuthenticationEntryPoint。