You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

修复方式

  1. 硬编码修改路径匹配:直接在Security配置中添加完整前缀
.requestMatchers("/api/v1/auth/**").permitAll()
  1. 使用配置变量(推荐):保持和控制器配置一致,避免硬编码
@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:24:56