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

Spring Security 6公开接口调用返回403 Forbidden问题求助

解决Spring Security 6中/api/auth/signup端点403 Forbidden问题

问题描述

我在使用Spring Security 6时遇到权限问题:已在SecurityConfig类中将/api/auth/signup端点配置为permitAll(无需认证),但发起POST请求http://localhost:8087/api/auth/signup/时,仍收到403 Forbidden响应。以下是我的相关代码:

SecurityConfig代码

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
  private final UserDetailsServiceImpl userDetailsService;
  private final JwtAuthFilter jwtAuthFilter;


  public SecurityConfig(UserDetailsServiceImpl userDetailsService, JwtAuthFilter jwtAuthFilter) {
    this.userDetailsService = userDetailsService;
    this.jwtAuthFilter = jwtAuthFilter;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }


  @Bean
  public SecurityFilterChain filterChain(
      HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
    return http.cors(AbstractHttpConfigurer::disable)
        .csrf(AbstractHttpConfigurer::disable)
        .sessionManagement(
            session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        //        Set permissions on endpoints
        .authorizeHttpRequests(
            auth ->
                auth
                    //            public endpoints
                    .requestMatchers(HttpMethod.POST, "/api/auth/signup/**")
                    .permitAll()
                    .requestMatchers(HttpMethod.POST, "/api/auth/login/**")
                    .permitAll()
                    .requestMatchers(HttpMethod.GET, "/authentication-docs/**")
                    .permitAll()
                    //             private endpoints
                    .anyRequest()
                    .authenticated())
        .authenticationManager(authenticationManager)
        .formLogin(form -> form.loginPage("/login"))
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
  }


  @Bean
  public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
    AuthenticationManagerBuilder authenticationManagerBuilder =
        http.getSharedObject(AuthenticationManagerBuilder.class);
    authenticationManagerBuilder
        .userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder());
    return authenticationManagerBuilder.build();
  }
}

UserDetailsServiceImpl代码

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

  private final UserRepository repository;

  public UserDetailsServiceImpl(UserRepository repository) {
    this.repository = repository;
  }

  @Override
  public UserDetails loadUserByUsername(String email) {


    UserEntity userEntity =
        repository
            .findByUsername(email)
            .orElseThrow(
                () ->
                    new NotFoundException(String.format("User does not exist, email: %s", email)));

    List<SimpleGrantedAuthority> grantedRoles = new ArrayList<>();
    grantedRoles.add(new SimpleGrantedAuthority(userEntity.getRole().name()));

    return org.springframework.security.core.userdetails.User.builder()
        .username(userEntity.getUsername())
        .password(userEntity.getPassword())
        .authorities(grantedRoles)
        .build();
  }
}

AuthController代码

@RestController
@RequestMapping(path = "/api/auth", produces = MediaType.APPLICATION_JSON_VALUE)
public class AuthController {

  private final AuthenticationManager authenticationManager;
  private final UserService userService;

  public AuthController(AuthenticationManager authenticationManager, UserService userService) {
    this.authenticationManager = authenticationManager;
    this.userService = userService;
  }
  
  @PostMapping("/signup")
  public UserDto signup(@Valid @RequestBody UserRequest requestDto) {
    return userService.signup(requestDto);
  }

  @PostMapping("/login")
  public ResponseEntity<UserResponse> login(@Valid @RequestBody UserRequest request) {
    try {
      authenticationManager.authenticate(
          new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
    } catch (BadCredentialsException e) {
      throw e;
    }

    String token = JwtHelper.generateToken(request.getUsername());
    return ResponseEntity.ok(new UserResponse(request.getUsername(), token));
  }
}

问题分析与修复方案

1. 请求路径匹配不一致

你的请求URL是/api/auth/signup/(带末尾斜杠),但Controller映射的是/signup(无斜杠),Spring Security默认路径匹配规则会将两者视为不同路径,导致permitAll配置未生效。

修复:
修改SecurityConfig中的路径配置,同时兼容带斜杠和不带斜杠的请求:

.requestMatchers(HttpMethod.POST, "/api/auth/signup", "/api/auth/signup/**")
.permitAll()

2. 重复启用方法安全注解

Spring Security 6中@EnableGlobalMethodSecurity已被弃用,你同时启用@EnableMethodSecurity和@EnableGlobalMethodSecurity会导致配置冲突,干扰权限判断。

修复:
删除@EnableGlobalMethodSecurity注解,保留@EnableMethodSecurity(prePostEnabled = true)即可:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    // ... 其他代码
}

3. JwtAuthFilter拦截了公共端点

你将JwtAuthFilter添加在UsernamePasswordAuthenticationFilter之前,如果Filter内部没有跳过公共端点的逻辑,会对permitAll的请求执行认证检查,导致403。

修复:
在JwtAuthFilter的doFilterInternal方法中添加跳过逻辑:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    String requestUri = request.getRequestURI();
    // 跳过无需认证的端点
    if (requestUri.startsWith("/api/auth/signup") || requestUri.startsWith("/api/auth/login")) {
        filterChain.doFilter(request, response);
        return;
    }
    // 原有JWT认证逻辑
    // ...
}

4. 冗余的表单登录配置

你配置了.formLogin(form -> form.loginPage("/login")),但这是针对传统表单登录的配置,与无状态JWT认证冲突,会干扰权限判定逻辑。

修复:
移除表单登录配置:

// 删除此行代码
.formLogin(form -> form.loginPage("/login"))

最终修改后的SecurityConfig

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
  private final UserDetailsServiceImpl userDetailsService;
  private final JwtAuthFilter jwtAuthFilter;

  public SecurityConfig(UserDetailsServiceImpl userDetailsService, JwtAuthFilter jwtAuthFilter) {
    this.userDetailsService = userDetailsService;
    this.jwtAuthFilter = jwtAuthFilter;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  public SecurityFilterChain filterChain(
      HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
    return http.cors(AbstractHttpConfigurer::disable)
        .csrf(AbstractHttpConfigurer::disable)
        .sessionManagement(
            session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(
            auth ->
                auth
                    .requestMatchers(HttpMethod.POST, "/api/auth/signup/**", "/api/auth/login/**")
                    .permitAll()
                    .requestMatchers(HttpMethod.GET, "/authentication-docs/**")
                    .permitAll()
                    .anyRequest()
                    .authenticated())
        .authenticationManager(authenticationManager)
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
  }

  @Bean
  public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
    AuthenticationManagerBuilder authenticationManagerBuilder =
        http.getSharedObject(AuthenticationManagerBuilder.class);
    authenticationManagerBuilder
        .userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder());
    return authenticationManagerBuilder.build();
  }
}

内容的提问来源于stack exchange,提问作者obela06

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 01:27:08