Spring Security中POST创建用户返回403 Forbidden问题求助
问题:Spring Security放行
/auth/**仍返回403 Forbidden 已在Spring Security配置类中放行/auth/**路径,但通过Postman发送POST请求创建新用户时仍收到403 Forbidden错误。禁用Spring Security(放行所有请求)后请求可正常执行。
终端日志
2024-12-19T00:21:09.253+01:00 DEBUG 15372 --- [backend-2in1-project] [nio-8080-exec-3] o.s.security.web.FilterChainProxy : Securing POST /api/v1/auth/register 2024-12-19T00:21:09.253+01:00 DEBUG 15372 --- [backend-2in1-project] [nio-8080-exec-3] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext 2024-12-19T00:21:09.253+01:00 DEBUG 15372 --- [backend-2in1-project] [nio-8080-exec-3] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
相关代码
Security配置类
package com.momentstock.backend._in1.project.security; import lombok.RequiredArgsConstructor; import org.apache.catalina.filters.CorsFilter; import org.modelmapper.ModelMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.client.RestTemplate; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.servlet.config.annotation.CorsRegistry; import java.util.List; @Configuration @EnableWebSecurity @RequiredArgsConstructor //@EnableGlobalMethodSecurity(prePostEnabled = true) // to enable @preAuthorize public class SecurityConfig { private final UserDetailsService userDetailsService; private final JwtAuthFilter jwtAuthFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .cors(cors -> cors.configurationSource(corsConfigurationSource())) .authorizeHttpRequests(con -> con .requestMatchers("/auth/**").permitAll() .requestMatchers("/users/**").hasRole("USER") .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/api/v1/orders/payment/callback").permitAll() .requestMatchers("/welcome/**").permitAll() // .requestMatchers("/testKey").permitAll() .requestMatchers(HttpMethod.DELETE, "/users/user/me/delete-account").authenticated() .anyRequest().authenticated() ) // .authorizeHttpRequests(auth -> auth .anyRequest().permitAll()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of( "https://api.flutterwave.com", "https://api.paystack.co", "https://sandbox.interswitchng.com" )); configuration.addAllowedOrigin("*"); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); configuration.setAllowedHeaders(List.of("*")); // Allow all headers configuration.setExposedHeaders(List.of("Authorization", "Content-Type")); // Optional, expose specific headers configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); // Apply to all endpoints return source; } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public ModelMapper modelmapper(){ return new ModelMapper(); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
认证控制器
@RestController @RequiredArgsConstructor @RequestMapping("${api.prefix}/auth") public class UserAuthController { private final IUserService userService; private final UserRepository userRepository; private final VerificationTokenRepository tokenRepository; private final JwtTokenProvider tokenProvider; @PostMapping("/register") public ResponseEntity<String> registerUser(@RequestBody CreateUserRequest request){ userService.registerUser(request); return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully, check your email complete verification"); } @PostMapping("/login") public ResponseEntity<ApiResponse> loginUser(@RequestBody LoginRequest request){ try { LoginResponse loginResponse = userService.loginUser(request); return ResponseEntity.ok(new ApiResponse("Success", loginResponse)); } catch (AuthenticationException e) { return ResponseEntity.status(BAD_REQUEST).body(new ApiResponse(e.getMessage(), null)); } } }
UserDetails实现类
package com.momentstock.backend._in1.project.security; import com.momentstock.backend._in1.project.enums.Role; import com.momentstock.backend._in1.project.models.User; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @Data public class UserRegDetails implements UserDetails { private String userName; private String password; private boolean isEnabled; private Role role; public UserRegDetails(User user) { this.userName = user.getEmail(); this.password = user.getPassword(); this.isEnabled = user.isEnabled(); this.role = Role.valueOf(user.getRole().name()); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return role != null ? List.of(new SimpleGrantedAuthority(role.name().toUpperCase())) : List.of(); } @Override public String getPassword() { return password; } @Override public String getUsername() { return userName; } @Override public boolean isAccountNonExpired() { // return UserDetails.super.isAccountNonExpired(); return true; } @Override public boolean isAccountNonLocked() { // return UserDetails.super.isAccountNonLocked(); return true; } @Override public boolean isCredentialsNonExpired() { // return UserDetails.super.isCredentialsNonExpired(); return true; } @Override public boolean isEnabled() { // return UserDetails.super.isEnabled(); return isEnabled; } }
UserDetailsService实现类
@Service @RequiredArgsConstructor public class UserRegDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { return userRepository.findByEmail(email).map(UserRegDetails::new).orElseThrow(() -> new UsernameNotFoundException("User not found")); } }
解决方案
问题核心是路径匹配不匹配:
- 控制器通过
@RequestMapping("${api.prefix}/auth")配置,结合请求路径/api/v1/auth/register,可知api.prefix的值为/api/v1,实际接口路径是/api/v1/auth/**。 - 但Security配置中
requestMatchers("/auth/**").permitAll()仅匹配/auth开头的路径,不包含/api/v1前缀,导致请求未被放行,触发403。
修复方式
- 硬编码修改路径匹配:直接在Security配置中添加完整前缀
.requestMatchers("/api/v1/auth/**").permitAll()
- 使用配置变量(推荐):保持和控制器配置一致,避免硬编码
@Value("${api.prefix}") private String apiPrefix; // 在authorizeHttpRequests中替换原有配置 .requestMatchers(apiPrefix + "/auth/**").permitAll()
额外检查
确认JwtAuthFilter不会拦截已放行的路径,可在过滤器的doFilterInternal方法中添加跳过逻辑:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String path = request.getRequestURI(); if (path.startsWith("/api/v1/auth/")) { filterChain.doFilter(request, response); return; } // 原有JWT校验逻辑 }
内容的提问来源于stack exchange,提问作者Landry Okoye
相关产品推荐
相关产品推荐

