Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
本节介绍Spring Security在Servlet身份验证中使用的主要架构组件。如果需要解释这些部分如何组合在一起的具体流程,请查看特定于身份验证机制的部分。
关系的简要示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]
这是 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 进行身份验证。SecurityContextHolderRememberMeServices.loginFail。如果没有配置 remeberme ,则不会进行任何操作AuthenticationFailureHandlerSessionAuthenticationStrategy 收到新登录通知。SecurityContextHolder 设置 Authentication 。稍后 SecurityContextPersistenceFilter 将 SecurityContext 保存到HttpSession 中.RememberMeServices.loginSuccess 。如果没有配置 remeberme ,则不会进行任何操作ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent 事件AuthenticationSuccessHandler 。
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();
}
}配置
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接口
创建 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 他所建模的概念是:“认证入口点”。它在用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程。
这为登陆失败时的流程:

ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其余部分
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();
}
}