连锁书店管理系统开发遇到的一些问题
Spring Sceurity未在SecurityConfig中加入jwt过滤器导致登录时需要jwt令牌
解决:
在SecurityConfig提前加入jwt过滤器,并放行login接口
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable() // 禁用 CSRF
.authorizeHttpRequests()
.requestMatchers("/manage/login").permitAll() // 允许匿名访问登录接口
.anyRequest().authenticated() // 其他请求需要认证
.and()
// 将 JwtFilter 添加到过滤器链中,放在 UsernamePasswordAuthenticationFilter 之前
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
为什么要把 JwtFilter 放在 UsernamePasswordAuthenticationFilter 之前?
优先级问题
UsernamePasswordAuthenticationFilter是专门处理表单登录的过滤器。如果请求需要基于 Token 的认证(如 JWT),那么JwtFilter应该优先处理请求。- 如果
UsernamePasswordAuthenticationFilter先执行,它会尝试处理所有请求,即使这些请求是通过 Token 认证的。这会导致不必要的认证逻辑执行,甚至可能覆盖JwtFilter设置的认证信息。
避免冲突
- 如果请求携带了有效的 Token,
JwtFilter会直接设置Authentication对象并放行请求。这样,UsernamePasswordAuthenticationFilter就不会被调用,避免了可能存在的冲突。 - 如果请求是登录请求(如
/api/login),JwtFilter会放行请求,UsernamePasswordAuthenticationFilter可以正常处理登录逻辑。
效率问题
JwtFilter通常只需要解析 Token 并验证其有效性,效率较高。如果先调用UsernamePasswordAuthenticationFilter,它可能会执行不必要的用户名密码认证逻辑,影响性能。
authenticationManager.authenticate()循环调用loadUserByUsername()方法
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisService redisService;
@Override
public Result login(StaffLogin staffLogin) {
//AuthenticationManager authenticate进行用户认证
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(staffLogin.getUsername(), staffLogin.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过,给出对应的提示
if (authenticate == null || !authenticate.isAuthenticated()) {
return Result.unauthorized("用户名或密码错误",null);
}
//如果认证通过,使用userid生成一个jwt jwt存入StaffInfo返回
LoginStaff loginStaff = (LoginStaff) authenticate.getPrincipal();
//生成jwt
Long id = loginStaff.getStaff().getId();
String jwtToken = genAccessToken(id);
//把完整的员工信息存入redis userid作为key
redisService.setValueWithExpire("Staff:"+id,loginStaff,3600);
//封装返回前端
List<String> authorityList = loginStaff.getPermissions();
StaffInfo staffInfo = loginStaff.getStaff().toStaffInfo(authorityList,jwtToken);
return Result.success("登录成功",staffInfo);
}
}
原因:使用了 @Bean 注解来创建 UserDetailsServiceImpl,但同时该类已经被标记为 @Service 注解。这会导致 Spring 容器中存在两个 UserDetailsService 实例,造成循环调用。
问题分析
- 重复实例化的问题:
- 你的
UserDetailsServiceImpl类有@Service注解,这会使 Spring 自动创建一个实例并管理它 - 同时,在
SecurityConfig中你又使用@Bean方法userDetailsService()手动创建了一个实例 - 结果是 Spring 容器中有两个
UserDetailsServiceImpl实例
- 你的
- 在 Spring Security 中的影响:
- 当有多个
UserDetailsServiceBean 时,Spring Security 可能会在不同的地方使用不同的实例 - 这会导致认证流程混乱,可能造成循环引用或循环调用
authenticationManager.authenticate()方法会使用其中一个实例,而其他地方可能使用另一个实例
- 当有多个
解决方案
修改你的代码,确保只有一个 UserDetailsService 实例:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private JwtFilter jwtFilter;
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Autowired
private UserDetailsServiceImpl userDetailsService; // 直接注入已有的 @Service 实例
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
// 使用注入的 userDetailsService 实例,而不是创建新的
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 其余配置保持不变
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers(PermitAllPaths.getPaths().toArray(new String[0])).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
这样修改后,你将:
- 删除了
@Bean public UserDetailsServiceImpl userDetailsService()方法 - 改为直接
@Autowired注入由@Service注解自动创建的单例 - 在
authenticationManager方法中使用注入的实例

浙公网安备 33010602011771号