Spring Security FilterChain失效但@PreAuthorize可用问题求助
问题原因排查与修复
核心问题:自定义过滤器未加入Spring Security过滤器链
你遇到的问题根源在于自定义的JwtAuthenticationProcessingFilter和CustomJsonUsernamePasswordAuthenticationFilter没有被添加到Spring Security的过滤器链中,导致权限校验时机错误:
- 当在
SecurityFilterChain中配置hasAnyRole("MANAGER")时,Spring Security的权限校验会在请求到达自定义JWT过滤器之前执行,此时SecurityContext中还没有用户的Authentication信息,因此权限校验失败,返回403。 - 改为
permitAll()后,Spring Security放行所有请求,请求会先经过Spring MVC的过滤器链(此时自定义JWT过滤器执行,将Authentication存入SecurityContext),再进入Controller层,@PreAuthorize注解此时能获取到已设置的Authentication,因此权限校验正常。
其他细节验证
- 你的
JwtAuthenticationProcessingFilter是OncePerRequestFilter的实现,作为Spring Bean会被自动注册到Spring MVC的过滤器链中,但这个链的执行顺序在Spring Security过滤器链之后,所以无法为Spring Security的FilterChain权限校验提供Authentication。 - 你在
saveAuthentication方法中用.roles(myAdmin.getRole().name())构建UserDetails,Spring Security会自动为角色添加ROLE_前缀,所以hasAnyRole("MANAGER")的写法是正确的,和@PreAuthorize("hasRole('ROLE_MANAGER')")等价。
修复方案
在SecurityConfig的filterChain方法中,将自定义过滤器添加到Spring Security的过滤器链中,确保JWT校验在权限校验之前执行:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(cs -> cs.disable()) .sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f->f.disable()) .httpBasic(h->h.disable()) // 添加自定义登录过滤器,放在UsernamePasswordAuthenticationFilter之前 .addFilterBefore(customJsonUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 添加JWT校验过滤器,放在登录过滤器之后、权限校验之前 .addFilterAfter(jwtAuthenticationProcessingFilter(), CustomJsonUsernamePasswordAuthenticationFilter.class); http .authorizeHttpRequests(auth-> auth.requestMatchers("/login", "/admins/**").permitAll() .requestMatchers("/lectures/**", "/instructors/**").hasAnyRole("MANAGER") .anyRequest().authenticated() ); return http.build(); }
额外建议
- 若仅使用FilterChain权限校验,可移除
@EnableGlobalMethodSecurity;若保留,需确保两种校验方式的规则一致。 - 可在过滤器和权限校验逻辑中添加日志,验证执行顺序是否符合预期。
附:相关代码(原代码整理)
SecurityConfig.java
@Configuration @EnableWebSecurity @RequiredArgsConstructor @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { private final LoginService loginService; private final JwtService jwtService; private final AdminRepository adminRepository; private final ObjectMapper objectMapper; @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(cs -> cs.disable()) .sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f->f.disable()) .httpBasic(h->h.disable()); http .authorizeHttpRequests(auth->{ // auth.requestMatchers("/lectures/**").hasAnyRole("MANAGER") // auth.requestMatchers("/instructors/**").hasAnyRole("MANAGER") // auth.requestMatchers("/instructors/**").authenticated() // .requestMatchers("/lectures/**").permitAll() //// .requestMatchers("/instructors/**").hasAnyRole("MANAGER") // .requestMatchers("/admins/**").permitAll() // .requestMatchers("/login").permitAll() //auth.requestMatchers("/instructors/**").hasRole("MANAGER") auth.anyRequest().permitAll() ; }); return http.build(); } @Bean public AuthenticationManager authenticationManager() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setPasswordEncoder(passwordEncoder()); provider.setUserDetailsService(loginService); return new ProviderManager(provider); } @Bean public LoginSuccessHandler loginSuccessHandler() { return new LoginSuccessHandler(jwtService, adminRepository); } @Bean public LoginFailureHandler loginFailureHandler() { return new LoginFailureHandler(); } @Bean public CustomJsonUsernamePasswordAuthenticationFilter customJsonUsernamePasswordAuthenticationFilter() { CustomJsonUsernamePasswordAuthenticationFilter customJsonUsernamePasswordLoginFilter = new CustomJsonUsernamePasswordAuthenticationFilter(objectMapper); customJsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager()); customJsonUsernamePasswordLoginFilter.setAuthenticationSuccessHandler(loginSuccessHandler()); customJsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler()); return customJsonUsernamePasswordLoginFilter; } @Bean public JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter() { JwtAuthenticationProcessingFilter jwtAuthenticationFilter = new JwtAuthenticationProcessingFilter(jwtService, adminRepository); return jwtAuthenticationFilter; } }
JwtAuthenticationProcessingFilter.java
@RequiredArgsConstructor @Slf4j public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter { private static final String NO_CHECK_URL = "/login"; private final JwtService jwtService; private final AdminRepository adminRepository; private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (request.getRequestURI().equals(NO_CHECK_URL)) { filterChain.doFilter(request, response); return; } String refreshToken = jwtService.extractRefreshToken(request) .filter(jwtService::isTokenValid) .orElse(null); if (refreshToken != null) { checkRefreshTokenAndReIssueAccessToken(response, refreshToken); return; } if (refreshToken == null) { checkAccessTokenAndAuthentication(request, response, filterChain); } } public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) { adminRepository.findByRefreshToken(refreshToken) .ifPresent(admin -> { String reIssuedRefreshToken = reIssueRefreshToken(admin); jwtService.sendAccessAndRefreshToken(response, jwtService.createAccessToken(admin.getEmail()), reIssuedRefreshToken); }); } private String reIssueRefreshToken(Admin admin) { String reIssuedRefreshToken = jwtService.createRefreshToken(); admin.updateRefreshToken(reIssuedRefreshToken); adminRepository.saveAndFlush(admin); return reIssuedRefreshToken; } public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { log.info("checkAccessTokenAndAuthentication() 호출"); jwtService.extractAccessToken(request) .filter(jwtService::isTokenValid) .ifPresent(accessToken -> jwtService.extractEmail(accessToken) .ifPresent(email -> adminRepository.findByEmail(email) .ifPresent(this::saveAuthentication))); log.info("saveAuthentication() 호출"); filterChain.doFilter(request, response); } public void saveAuthentication(Admin myAdmin) { String password = myAdmin.getPassword(); UserDetails userDetailsUser = org.springframework.security.core.userdetails.User.builder() .username(myAdmin.getEmail()) .password(password) .roles(myAdmin.getRole().name()) .build(); Authentication authentication = new UsernamePasswordAuthenticationToken(userDetailsUser, null, authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities())); SecurityContextHolder.getContext().setAuthentication(authentication); } }
InstructorController.java
@RequiredArgsConstructor @RequestMapping("/instructors") @RestController public class InstructorController { private final InstructorService instructorService; @PostMapping @PreAuthorize("hasRole('ROLE_MANAGER')")//it works public ResponseEntity createInstructor(@Valid @RequestBody InstructorPostDto instructorPostDto) { InstructorResponseDto createdInstructor = instructorService.createInstructor(instructorPostDto); return new ResponseEntity<>(createdInstructor, HttpStatus.CREATED); } }
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '3.1.7' id 'io.spring.dependency-management' version '1.1.4' } group = 'com.hh99' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { // security 관련 의존성 implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' // jwt 관련 의존성 implementation 'com.auth0:java-jwt:4.2.1' } tasks.named('bootBuildImage') { builder = 'paketobuildpacks/builder-jammy-base:latest' } tasks.named('test') { useJUnitPlatform() }
内容的提问来源于stack exchange,提问作者Simba
相关产品推荐
相关产品推荐

