SpingSecurity认证流程与使用
1、Security的本质:
SpringSecurity的本质就是一个过滤器链,内部包含了提供各种功能的过滤器,基本案例中的过滤器链如下图所示(仅展示了部分核心过滤器)
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。基本案例的认证工作主要有它负责
ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException
FilterSecurityInterceptor:负责权限校验的过滤器
可以通过Debug查看SpingSecurity过滤器链中有哪些过滤器以及它们的先后顺序
2、Security认证流程
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息
AuthenticationManager接口:定义了认证Authentication的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中
UsernamePasswordAuthenticationFilter实现类:实现了我们最常用的基于用户名和密码的认证逻辑,封装Authentication对象
DaoAuthenticationProvider实现类:是AuthenticationManager中管理的其中一个Provider,因为是要访问数据库,所以叫Dao准备
3、Security的实现流程
3.1 添加依赖
启动一个Springboot项目,并在pom.xml添加对应的Spring Security的相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3.2 基础配置
Spring Security 默认对所有 HTTP 请求进行保护,要求用户进行身份验证。我们可以通过配置类来自定义这个行为。
创建自定义的 Security 配置类:
需要创建一个继承 WebSecurityConfigurerAdapter 的配置类,或者在 Spring Security 5.7 后使用 SecurityFilterChain 来配置安全性。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/", "/home").permitAll() // 允许首页和home不认证
.anyRequest().authenticated() // 其他所有请求都需要认证
)
.formLogin((form) -> form
.loginPage("/login") // 自定义登录页
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user")
.password(passwordEncoder.encode("password"))
.roles("USER").build());
manager.createUser(User.withUsername("admin")
.password(passwordEncoder.encode("admin"))
.roles("ADMIN").build());
return manager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3.3 认证流程(Authentication)
Spring Security 的认证流程是通过 Authentication 机制来实现的。
步骤:
1.提交登录请求:当用户通过表单登录时,Spring Security 内置的 UsernamePasswordAuthenticationFilter 过滤器会拦截登录请求(默认拦截 /login)。
2.封装认证对象:UsernamePasswordAuthenticationFilter 会将用户提交的用户名和密码封装成一个 UsernamePasswordAuthenticationToken 对象,该对象实现了 Authentication 接口。
3.调用 AuthenticationManager:这个封装好的 Authentication 对象会被传递给 AuthenticationManager,通常是由 ProviderManager 实现,来执行认证。
4.使用 AuthenticationProvider 验证:ProviderManager 内部维护了多个 AuthenticationProvider(通常是 DaoAuthenticationProvider)。每个 AuthenticationProvider 都会尝试根据特定的策略对用户进行认证。
5.UserDetailsService 和 UserDetails:DaoAuthenticationProvider 会使用 UserDetailsService 加载用户信息,UserDetailsService 返回一个包含用户信息的 UserDetails 对象,这个对象包含用户名、密码、角色等信息。
6.密码校验:DaoAuthenticationProvider 会调用 PasswordEncoder 对密码进行加密和匹配,如果认证成功,返回一个 Authentication 对象,包含用户的完整信息和权限。
7.认证成功/失败处理:如果认证成功,用户会被重定向到上一个请求页面。如果失败,则返回登录页面,并显示错误信息。
@Override
public Response login(UserDTO user) {
// 创建一个新的 UsernamePasswordAuthenticationToken,用于封装用户提交的用户名和密码。
// 此对象将在认证过程中传递给 AuthenticationManager 进行认证(而不是直接调用默认的校验方式)。
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
try {
// 将封装好的 UsernamePasswordAuthenticationToken 对象传递给 authenticationManager,并使用 authenticate 方法进行认证。
// 此认证过程涉及的步骤包括 AuthenticationProvider 处理(如 DaoAuthenticationProvider 调用 UserDetailsService 和 PasswordEncoder)。
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 成功认证后,获取 LoginUser 对象并从中提取用户 ID。
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwt = jwtUtils.generateToken(userid);
HashMap<String, String> map = new HashMap<>();
map.put("token", jwt);
redisTemplate.opsForValue().set(userid,loginUser);
return new Response<>(200,"success",map);
}
catch (BadCredentialsException | UsernameNotFoundException e) {
return new Response<>(-1, e.getMessage(),null);
}
}
3.4 授权流程(Authorization)
授权是决定用户是否有权访问某些资源的过程。在 Spring Security 中,通过配置 HttpSecurity 对不同的 URL 路径进行权限控制。
授权的基本步骤:
1.配置授权规则:在 SecurityConfig 中,使用 authorizeHttpRequests() 配置不同路径的访问权限。例如,可以指定某些路径对特定角色开放。
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
);
2.角色和权限:角色是基于 GrantedAuthority 的字符串表示。例如,ROLE_USER 或 ROLE_ADMIN。Spring Security 会根据 UserDetails 中的角色信息来决定是否授予用户访问特定资源的权限。
3.访问控制:当用户发起请求时,Spring Security 会检查该用户的角色是否符合指定的访问控制规则。如果符合,用户将被允许访问,否则将被拒绝,并重定向到登录页面或显示 403 错误页面。