Spring Security 如何在SpringBoot 2.7中配置CSRF Token

8mmmxcuj  于 2023-10-20  发布在  Spring
关注(0)|答案(1)|浏览(113)

我们正在使用SpringBoot 2.7构建一个简单的应用程序,并且遇到了CSRF令牌设置的问题。用户操作(POST请求)返回403 forbidden。这包含在AWS ecs集群中,并运行SpringBoot 2.7和SpringBootSecurity 5.8。csrf令牌阻止了post操作,我不确定正确的设置是什么样子。我们只能在禁用CSRF的情况下执行操作。

编辑:进一步排查后,我们可以看到文件上传的post请求中没有xsrf令牌响应头。

这是当前的设置。

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);
    }
}

安全配置:

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.config.http.SessionCreationPolicy;
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())
                        // https://stackoverflow.com/a/74521360/65681
                        .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));

            }
        };
    }

}

前台岗位:

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);
            }
        }
    };
nmpmafwu

nmpmafwu1#

你试过用Stack Overflow answer吗?它建议将这一行添加到安全配置的过滤器链中:

http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);

CsrfTokenResponseHeaderBindingFilter.java定义为:

public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
    protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
    protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
    protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
    protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
        CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);

        if (token != null) {
            response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName());
            response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName());
            response.setHeader(RESPONSE_TOKEN_NAME, token.getToken());
        }

        filterChain.doFilter(request, response);
    }
}

相关问题