Spring Boot双JWT认证过滤器配置引发403访问禁止问题
Spring Boot双过滤链JWT认证403问题排查方向
我正在开发一个Spring Boot应用,需要两条安全过滤链和两个JWT认证过滤器。完成现有实现后,使用Postman携带已认证的Bearer Token对/api/affiliates发起GET请求时,返回403禁止访问状态,而非预期的OK状态,以下是相关代码及排查方向:
实体类:Affiliate.java
@Entity @Data public class Affiliate implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String firstName; private String lastName; private String sex; @Column(unique = true) private String phoneNumber; @Column(unique = true) private String email; private String password; @Column(name = "balance_in_naira") private int balance; private String referralCode; @Enumerated(value = EnumType.STRING) private Role role = Role.AFFILIATE; @OneToMany private List<Student> students; private boolean enabled; public void increaseBalance(int referralBonus) { balance += referralBonus; } public void setPassword(String password) { this.password = new BCryptPasswordEncoder().encode(password); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return Collections.singleton(new SimpleGrantedAuthority("ROLE_" + getRole().name())); } @Override public String getUsername() { return email; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } }
安全配置:SecurityConfig.java
@Configuration @EnableWebSecurity @AllArgsConstructor public class SecurityConfiguration { private final StudentJwtAuthorizationFilter studentJwtAuthorizationFilter; private final StudentDetailsService studentDetailsService; private final AffiliateDetailsService affiliateDetailsService; private final AffiliateJwtAuthorizationFilter affiliateJwtAuthorizationFilter; @Bean @Order(1) public SecurityFilterChain affiliateFilterChain(HttpSecurity http) throws Exception { return http .securityMatcher("/api/affiliates", "/api/affiliates/**") .cors().disable() .csrf().disable() .formLogin().disable() .httpBasic().disable() .authorizeHttpRequests() .requestMatchers(HttpMethod.POST, "/api/affiliates").permitAll() .requestMatchers("/api/affiliates/confirm", "/api/affiliates/auth", "/api/affiliates/forgot-password", "/api/affiliates/forgot-password/**").permitAll() .requestMatchers("/api/affiliates", "/api/affiliates/referrals").hasRole(Role.AFFILIATE.name()) .anyRequest().authenticated() .and() .authenticationProvider(affiliateAuthenticationProvider()) .addFilterBefore(affiliateJwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class) .build(); } @Bean public SecurityFilterChain studentFilterChain(HttpSecurity http) throws Exception { return http .securityMatcher("/api/students", "/api/students/**") .cors().disable() .csrf().disable() .httpBasic().disable() .formLogin().disable() .authorizeHttpRequests() .requestMatchers(HttpMethod.POST, "/api/students").permitAll() .requestMatchers("/api/students/auth", "/api/students/forgot-password", "/api/students/forgot-password/**").permitAll() .requestMatchers("/api/students/confirm").permitAll() .requestMatchers("/api/students/complete-registration").hasRole(Role.STUDENT.name()) .requestMatchers(HttpMethod.GET, "/api/students").hasRole(Role.STUDENT.name()) .requestMatchers(HttpMethod.PUT, "/api/students").hasRole(Role.STUDENT.name()) .requestMatchers(HttpMethod.PATCH, "/api/students/new-password").hasRole(Role.STUDENT.name()) .requestMatchers(HttpMethod.DELETE, "/api/students").hasRole(Role.STUDENT.name()) .anyRequest().authenticated() .and() .authenticationProvider(studentAuthenticationProvider()) .addFilterBefore(studentJwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class) .build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationProvider studentAuthenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(studentDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationProvider affiliateAuthenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(affiliateDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationManager authenticationManager(List<AuthenticationProvider> authenticationProviders) throws Exception { return new ProviderManager(authenticationProviders); } }
AffiliateJwtAuthorizationFilter.java
@Component @AllArgsConstructor public class AffiliateJwtAuthorizationFilter extends OncePerRequestFilter { private final AffiliateDetailsService detailsService; private final JwtService jwtService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authValue = request.getHeader("Authorization"); if (authValue != null && authValue.startsWith("Bearer ")) { String token = authValue.substring(7); Affiliate affiliate = detailsService.loadUserByUsername(jwtService.extractUsername(token)); if (!jwtService.isExpired(token)) { UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(affiliate, null, affiliate.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); response.setHeader("Authorization", request.getHeader("Authorization")); } } filterChain.doFilter(request, response); } }
StudentJwtAuthorizationFilter.java
@Component //make this a bean @AllArgsConstructor public class StudentJwtAuthorizationFilter extends OncePerRequestFilter { private final StudentDetailsService detailsService; private final JwtService jwtService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authValue = request.getHeader("Authorization"); if (authValue != null && authValue.startsWith("Bearer ")) { String token = authValue.substring(7); Student student = detailsService.loadUserByUsername(jwtService.extractUsername(token)); if (!jwtService.isExpired(token)) { UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(student, null, student.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); response.setHeader("Authorization", request.getHeader("Authorization")); //Check whether this is } } filterChain.doFilter(request, response); } }
控制器:AffiliateController.java
@RestController @RequestMapping(path = "/api/affiliates") @AllArgsConstructor public class AffiliateController { private final AffiliateService affiliateService; @GetMapping public AffiliateDto get() { return affiliateService.get(); } }
排查方向:
- JWT Token有效性验证
- 确认
jwtService.isExpired(token)逻辑是否正确,是否错误判断token过期时间 - 检查
jwtService.extractUsername(token)提取的用户名是否与数据库中Affiliate的email字段完全一致
- 确认
- 权限配置校验
- 虽然
hasRole(Role.AFFILIATE.name())会自动拼接ROLE_前缀,与你返回的ROLE_AFFILIATE权限匹配,但仍需确认Role枚举的AFFILIATE值是否正确 - 验证
securityMatcher("/api/affiliates", "/api/affiliates/**")是否覆盖了GET/api/affiliates请求
- 虽然
- 过滤器逻辑检查
- 添加日志打印,确认是否进入了设置
Authentication的代码块;如果token无效(比如过期),是否导致SecurityContext未被正确设置 - 检查
SecurityContextHolder.getContext().setAuthentication(auth)是否生效,后续请求中Authentication是否被意外清空 - 尝试注释掉
response.setHeader("Authorization", request.getHeader("Authorization")),确认这行代码是否干扰了请求头传递
- 添加日志打印,确认是否进入了设置
- 用户状态确认
- 检查数据库中对应Affiliate的
enabled字段是否为true,因为UserDetails的isEnabled()会返回该值,状态为false会导致认证失败
- 检查数据库中对应Affiliate的
- UserDetailsService与认证提供者检查
- 确认
AffiliateDetailsService.loadUserByUsername()是否正确返回对应的Affiliate对象,不存在查询空指针或返回null的情况 - 验证
affiliateAuthenticationProvider是否正确关联了affiliateDetailsService
- 确认
- 日志追踪
- 开启Spring Security的DEBUG日志:
查看认证流程日志,确认是否有认证成功记录,以及授权阶段的权限检查细节logging.level.org.springframework.security=DEBUG
- 开启Spring Security的DEBUG日志:
内容的提问来源于stack exchange,提问作者divad
相关产品推荐
相关产品推荐

