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

Spring Boot POST请求返回403 Forbidden,GET请求正常的问题求助

问题:POST请求返回403 Forbidden,但GET请求正常(已启用Google OAuth2登录)

我正在开发一个用于练习Java的简易待办列表应用,目前已启用Google登录功能,但遇到如下问题:前端发送POST请求POST http://localhost:3000/api/v1/list/create时返回403 (Forbidden)错误,而GET请求可正常返回200 OK。

相关代码与配置

ListController代码

package com.taskboard.app.controller;

import com.taskboard.app.model.Lists;
import com.taskboard.app.service.ListsService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.rmi.ServerException;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/v1/list")
public class ListController {

     private final ListsService listsService;

     public ListController(ListsService listsService) {
         this.listsService = listsService;
     }

     @PostMapping(
             path = "/create",
             consumes = "application/json",
             produces = "application/json"
     )
     public ResponseEntity<Lists> createList(@RequestBody Lists list) throws ServerException {
         Lists newlist = listsService.createList(list);
         if (newlist == null) {
             throw new ServerException("List not created");
         } else {
             return new ResponseEntity<>(newlist, HttpStatus.CREATED);
         }
     }

     @GetMapping(path = "/all",
             produces = "application/json")
     public List<Lists> getAllLists() {
         System.out.println("printing name from getall lists");
         return listsService.getAllLists();
     }

     @GetMapping(path = "{id}")
     public Optional<Lists> getList(@PathVariable("id") String id) {
         return listsService.getList(id);
     }
}

UserController代码

// create a UserController
package com.taskboard.app.controller;

import com.taskboard.app.model.User;
import com.taskboard.app.service.UserService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/user")
public class UserController {

    private Map userDetails;

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/all")
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping
    public Object login (OAuth2AuthenticationToken oAuth2AuthenticationToken) {
        userDetails = oAuth2AuthenticationToken.getPrincipal().getAttributes();
        return new RedirectView("http://localhost:3000");
    }

    @GetMapping("/current")
    public Object currentUser(OAuth2AuthenticationToken oAuth2AuthenticationToken) {
        userDetails = oAuth2AuthenticationToken.getPrincipal().getAttributes();
        return userDetails;
    }
}

application.properties配置

spring.datasource.url=jdbc:postgresql://localhost:5432/taskboard
spring.datasource.username=<username>
spring.datasource.password=<password>
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.security.oauth2.client.registration.google.client-id=<client-id-here>
spring.security.oauth2.client.registration.google.client-secret=<client-secret-here>

前端请求代码

export async function postList(list) {
    return await axios.post('/api/v1/list/create', list).then((response) => response.data).catch((error) => console.error(error))
}

解决思路与方案

问题核心原因:Spring Security默认对非GET请求强制校验CSRF令牌,你当前的配置中没有处理CSRF令牌的传递,且缺少明确的Security权限配置,导致POST请求被拦截返回403。

方案1:正确处理CSRF令牌(生产环境推荐)

后端配置

创建Spring Security配置类,开放必要接口并配置CSRF令牌的Cookie传递:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 配置接口权限
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/oauth2/**", "/login/**", "/api/v1/user/current").permitAll()
                .anyRequest().authenticated()
            )
            // 配置OAuth2登录跳转
            .oauth2Login(oauth2 -> oauth2
                .defaultSuccessUrl("http://localhost:3000", true)
            )
            // 配置CSRF令牌通过Cookie传递,允许前端读取
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}

前端修改

开启Cookie携带,并从Cookie中提取CSRF令牌添加到请求头:

import axios from 'axios';

// 允许axios携带Cookie
axios.defaults.withCredentials = true;

export async function postList(list) {
    // 从Cookie中提取XSRF-TOKEN
    const getCookie = (name) => {
        let cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            const cookies = document.cookie.split(';');
            for (let i = 0; i < cookies.length; i++) {
                const cookie = cookies[i].trim();
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    };

    const xsrfToken = getCookie('XSRF-TOKEN');
    return await axios.post('/api/v1/list/create', list, {
        headers: {
            'X-XSRF-TOKEN': xsrfToken
        }
    }).then(res => res.data).catch(err => console.error(err));
}

方案2:临时禁用CSRF(仅开发测试用)

如果只是开发阶段快速验证功能,可以临时关闭CSRF校验,但生产环境必须开启:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/oauth2/**", "/login/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .defaultSuccessUrl("http://localhost:3000", true)
            )
            .csrf(csrf -> csrf.disable()); // 禁用CSRF
        return http.build();
    }
}

额外检查点

  • 确认前端开启了withCredentials,确保登录状态的Cookie能传递到后端
  • 验证用户是否已完成Google登录,未登录状态下所有需要认证的接口都会返回403

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:34:58