实现功能就是继承这几个对应功能的类。
Spring Security 的过滤器(Filters)和拦截器(Interceptors)是 Spring Security 框架中用于保护 web 应用安全的重要组件。它们在处理 HTTP 请求时扮演不同的角色,并且相互配合以确保安全性。让我用一个更通俗易懂的方式来解释它们的作用和协作方式。
过滤器(Filters):
拦截器(Interceptors):
他们如何协作:
Spring Security 的过滤器和拦截器就像是一系列安全检查点,确保只有合法和安全的请求可以通过并被处理。过滤器更注重于安全性,而拦截器则提供了更多的灵活性和精细的控制。
SpringSecurity的基础配置,配置“/user/login”登录不登录都可以访问,其它接口必须要经过过滤器,这里配置了自定义的JWT过滤器,每个请求过来的时候都会由JWT过滤器进行判断与放行:
如果登录了,就把token对应的用户信息放到SpringSecurityCentext中并放行,没登录的直接放行,因为没有放入用户信息,所以SpringSecurity就会直接返回403。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 继承WebSecurityConfigurerAdapter来自定义安全配置
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // 自动注入JWT认证过滤器
@Bean // 标识返回的对象应该被Spring容器管理
public PasswordEncoder passwordEncoder() {
// 定义密码编码器,使用BCrypt强哈希算法
return new BCryptPasswordEncoder();
}
@Bean // 定义一个Spring管理的Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// 重写方法以返回AuthenticationManager,用于处理认证请求
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定义如何通过拦截器保护请求
http
.csrf().disable() // 禁用CSRF保护
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置为无状态,不依赖Session
.and()
.authorizeRequests() // 开始定义URL保护规则
.antMatchers("/user/login").anonymous() // “/user/login”无需认证即可访问
.anyRequest().authenticated() // 其他所有请求都需要认证
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 在UsernamePasswordAuthenticationFilter之前添加自定义JWT过滤器
}
}
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
filterChain.doFilter(request, response);
return;
}
//解析token
String userId = "";
try {
Claims claims = JwtUtil.parseJWT(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:" + userId;
// 从Redis中获取JSON字符串
JSONObject jsonObject = redisCache.getCacheObject(redisKey);
LoginUser loginUser = jsonObject.toJavaObject(LoginUser.class);
if (Objects.isNull(loginUser)) {
throw new RuntimeException("token非法,或者用户登录超时");
}
//存入SecurityContextHolder
//TODO 获取权限信息封装到Authentication中
//必须要使用三个构造函数的,这个构造函数中有确定已经认证的代码
//UsernamePasswordAuthenticationToken的构造函数,两个参数适用于登录的,三个参数适用于认证的
//两个参数 → 用户提交的未经验证的身份信息。三个参数 → 已经经过验证的身份信息,带有权限集合。
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//请求放行
filterChain.doFilter(request, response);
}
}
这里省略control,直接从service开始,一行一行的讲:
@Service
public class LoginServiceImpl implements LoginService {
// 自动注入AuthenticationManager,用于处理认证请求
@Autowired
private AuthenticationManager authenticationManager;
// 自动注入RedisCache,用于缓存相关操作
@Autowired
private RedisCache redisCache;
// 登录方法
@Override
public ResponseResult login(User user) {
// 创建UsernamePasswordAuthenticationToken用于身份验证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
// 调用AuthenticationManager进行身份验证
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 如果认证失败(返回null),抛出异常
if (Objects.isNull(authenticate)) {
throw new RuntimeException("用户名或密码错误");
}
// 认证成功后,使用用户ID生成JWT token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
// 将认证信息存储到Redis中
redisCache.setCacheObject("login:" + userId, loginUser);
// 构造响应数据,包含JWT token
HashMap map = new HashMap<>();
map.put("token", jwt);
return new ResponseResult(200, "登陆成功", map);
}
}
让AuthenticationManager加入到Spring管理:
@Bean // 定义一个Spring管理的Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// 重写方法以返回AuthenticationManager,用于处理认证请求
return super.authenticationManagerBean();
}
1、这行是用于认证,参数为账号与密码, 创建了一个包含用户名和密码的认证令牌。此时,该令牌的Authenticated属性为false,因为它仅表示一个待验证的认证请求。
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
2、将这个令牌传递给AuthenticationManager的authenticate方法后,开始了实际的认证过程。
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
这行代码内部会调用UserDetailsService实现类中的loadUserByUsername方法,这个方法会返回对应账号的所有信息。
authenticationManager.authenticate会根据返回的用户信息与前端传入的做比较:
下面是UserDetailsService的实现与LoginUser的实现:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName, username);
User user = userMapper.selectOne(queryWrapper);
if (Objects.isNull(user)){
throw new RuntimeException("用户名或密码错误,用户名不存在");
}
//TODO 查询对应权限信息
return new LoginUser(user);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
// 用户实体类,用于存储用户信息
private User user;
// 获取用户的权限集合。在这个例子中,返回null意味着没有指定权限
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return null;
}
// 获取用户的密码,用于认证过程中的密码校验
@Override
public String getPassword() {
return user.getPassword();
}
// 获取用户的用户名,用于认证过程中的用户识别
@Override
public String getUsername() {
return user.getUserName();
}
// 账户是否未过期。返回true表示账户未过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账户是否未被锁定。返回true表示账户未被锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 凭证(密码)是否未过期。返回true表示密码未过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 账户是否可用。返回true表示账户是可用的
@Override
public boolean isEnabled() {
return true;
}
}
3、剩下的其它的代码就很常规了。
Spring Security的AuthenticationManager.authenticate接口设计成可以接受不同类型的Authentication实现,而UsernamePasswordAuthenticationToken只是这些实现之一。这种设计提供了极大的灵活性和扩展性,允许Spring Security支持各种不同的认证机制。下面是一些详细解释:
UsernamePasswordAuthenticationToken:这是最常见的Authentication实现之一,通常用于基本的用户名和密码认证。
它主要用于表单登录或任何需要用户名和密码的场景。
Spring Security还提供了其他Authentication实现,例如RememberMeAuthenticationToken、JwtAuthenticationToken等。
每种实现都有其特定用途。例如,RememberMeAuthenticationToken用于记住我功能,而JwtAuthenticationToken可能用于基于JWT的认证。
开发者还可以根据需要创建自定义的Authentication实现,以支持特定的认证机制。
每种认证类型可能需要携带不同的信息。例如,JWT认证可能需要携带解析后的令牌信息,而传统的表单登录只需要用户名和密码。
不同类型的Authentication对象可能需要通过不同的AuthenticationProvider进行处理。例如,UsernamePasswordAuthenticationToken可能由DaoAuthenticationProvider处理,而JWT令牌可能由专门处理JWT的提供者处理。
通过支持不同类型的Authentication实现,Spring Security能够提供一个统一的框架来处理多种认证机制,从而增加了框架的通用性和灵活性。这样,开发者可以根据自己的安全需求和业务逻辑选择或扩展适当的认证类型。
这里省略control,直接从service开始,一行一行的讲:
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult logout() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userid = loginUser.getUser().getId();
redisCache.deleteObject("login:"+userid);
return new ResponseResult(200,"退出成功");
}
}
1、从SecurityContextHolder获取当前的安全上下文(SecurityContext),并从中检索当前认证的用户信息(Authentication对象)。这是获取当前登录用户详细信息的标准方法。
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
2、从Authentication对象中提取Principal,它代表了当前已认证的用户。在这个场景中,Principal被转换(或“强制转换”)为LoginUser类型,这是一个自定义的用户类。
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
3、剩下的代码就很常规了,因为登录的时候通过token中的userId作为key,User的JSON作为value存储到redis中,接下来的代码把这个key从redis中删除就可以了,就退出登录了。
所有认证相关的配置都在继承WebSecurityConfigurerAdapter 类中重写的configure方法里。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 继承WebSecurityConfigurerAdapter来自定义安全配置
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // 自动注入JWT认证过滤器
@Bean // 标识返回的对象应该被Spring容器管理
public PasswordEncoder passwordEncoder() {
// 定义密码编码器,使用BCrypt强哈希算法
return new BCryptPasswordEncoder();
}
@Bean // 定义一个Spring管理的Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// 重写方法以返回AuthenticationManager,用于处理认证请求
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定义如何通过拦截器保护请求
http
.csrf().disable() // 禁用CSRF保护
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置为无状态,不依赖Session
.and()
.authorizeRequests() // 开始定义URL保护规则
.antMatchers("/user/login").anonymous()// 只有不认证的才可以访问
.antMatchers("/xxx").permitAll()//有没有认证都可以访问
.anyRequest().authenticated() // 其他所有请求都需要认证
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
/*
addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
所有的请求到达之前都要先经过jwt过滤器,如果jwt过滤器没有添加认证就直接返回403
jwt在UsernamePasswordAuthenticationFilter之前执行
*/
}
}
permitAll():有没有认证的才能访问
anonymous():没有认证的才能访问
authenticated():必须要认证才能访问
未完待续...