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

SpringBoot 2.7中CSRF Token配置异常:POST请求返回403求助

SpringBoot 2.7 + Spring Security 5.8 CSRF Token配置问题:POST请求返回403 Forbidden

我们正在构建基于SpringBoot 2.7的应用,部署在AWS ECS集群中,搭配Spring Security 5.8。目前遇到CSRF Token配置问题:用户POST操作返回403 Forbidden,仅禁用CSRF时操作才能正常执行。

补充排查:调试发现文件上传的POST请求中没有XSRF Token响应头。


SpringBoot依赖

implementation("org.springframework.boot:spring-boot-starter-web:2.7.12")
implementation("org.springframework.boot:spring-boot-starter-security:2.7.12")
implementation("io.jsonwebtoken:jjwt:0.9.1")
implementation("com.jcraft:jsch:0.1.54")
implementation("software.amazon.awssdk:secretsmanager:2.20.79")
implementation("org.springframework.security:spring-security-web:5.8.0")
implementation("org.springframework.security:spring-security-config:5.8.0")
implementation("org.springframework.security:spring-security-core:5.8.0")
implementation("com.sun.xml.bind:jaxb-core:2.3.0.1")
implementation("javax.xml.bind:jaxb-api:2.3.1")
implementation("com.sun.xml.bind:jaxb-impl:2.3.1")

JWT过滤器

package com.place.sftp.portal.security;

import com.place.sftp.portal.constant.Path;
import com.place.sftp.portal.model.SecretHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Component
public class JwtFilter extends OncePerRequestFilter
{
    private static final String AUTHORIZATION = "Authorization";
    private static final String BEARER = "Bearer ";
    private static final String OPTIONS = "OPTIONS";

    @Autowired
    private SecretHolder secretHolder;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException
    {

        if(
                Path.AUTHENTICATE_FULL.equals(request.getRequestURI()) ||
                        Path.IS_CONNECTED_FULL.equals(request.getRequestURI()) ||
                        OPTIONS.equals(request.getMethod()))
        {
            logger.info("start: doFilterInternal");
            CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
            response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
            filterChain.doFilter(request, response);
        }
        else
        {
            logger.info("start: processJWT");
            processJWT(request, response, filterChain);
        }
    }

    private void processJWT(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException
    {
        logger.info("Processing JWT");
        String authorizationHeader = request.getHeader(AUTHORIZATION);
        if(validAuthHeader(authorizationHeader))
        {
            String token = authorizationHeader.substring(7);
            try
            {
                validateJWT(token, filterChain, request, response);
                logger.info("JWT validated successfully");
            }
            catch (Exception e)
            {
                logger.error("error in if ",e);
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
        else
        {
            logger.info("Error in else");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }

    private boolean validAuthHeader(String authorizationHeader)
    {
        return authorizationHeader != null && authorizationHeader.startsWith(BEARER);
    }

    private void validateJWT(String token, FilterChain filterChain,
                             HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        logger.info("starting: JWT Validation");
        logger.info("token is");
        logger.info(token);
        logger.info("secret is");
        //logger.info(secretHolder.getSecret());
        UsernamePasswordAuthenticationToken auth = SECRET_VALUE_HERE
        logger.info("got auth");
        SecurityContextHolder.getContext().setAuthentication(auth);
        logger.info("set security context successfully");
        CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        logger.info("ending: JWT Validation");
        response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
        filterChain.doFilter(request, response);
    }
}

安全配置(Security Config)

package com.place.sftp.portal.security;

import com.place.sftp.portal.constant.Path;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SimpleSavedRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@Configuration
@EnableWebSecurity
public class SecurityConfig
{
    private final Logger logger = LogManager.getLogger(SecurityConfig.class);

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception
    {
        logger.info("Configure CSRF Token.");

        http
                .authorizeHttpRequests((authz) -> authz.requestMatchers("/", "/index.html", "/static/**",
                                "/*.ico", "/*.json", "/*.png", "/api/isconnected", "/api/listchildnodes","/api/authenticate","/api/upload").permitAll()
                        .anyRequest().authenticated()
                )
                .csrf((csrf) -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
                )
                .addFilterAfter(new JwtFilter(), BasicAuthenticationFilter.class);
        logger.info("Authorize CSRF Token.");

        return http.build();
    }

    @Bean
    public RequestCache refererRequestCache() {
        return new HttpSessionRequestCache() {
            @Override
            public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
                String referrer = request.getHeader("referer");
                if (referrer == null) {
                    referrer = request.getRequestURL().toString();
                }
                request.getSession().setAttribute("SPRING_SECURITY_SAVED_REQUEST",
                        new SimpleSavedRequest(referrer));

            }
        };
    }

}

前端POST请求代码

const UploadModal = ({ open, handleClose }: UploadModalProps) => {
    const [fileData, setFileData] = useState<File | null>(null);
    const folderPath = "";
    const [cookies] = useCookies(["XSRF-TOKEN"]);

    const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        if (event.target.files) {
            setFileData(event.target.files[0]);
        }
    };

    const handleFileUpload = async () => {
        if (fileData) {
            const formData: FormData = new FormData();
            formData.append("file", fileData);
            formData.append("folderPath", folderPath);
            try {
                const response = await axios.post(
                    `${process.env.API_URL}/api/upload`,
                    formData,
                    {
                        withCredentials: true,
                        headers: { "X-XSRF-TOKEN": cookies["XSRF-TOKEN"] }
                    }
                );
                if (response) {
                    toast.success(
                        "File Upload Successfully",
                        successToastOptions
                    );

                    setFileData(null);
                } else {
                    toast.error("File Upload Failed", errorToastOptions);
                }
            } catch (error) {
                toast.error("File Upload Failed", errorToastOptions);
            }
        }
    };

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 00:55:55