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

部署在Azure上的Spring Boot+React应用CORS认证问题求助

部署Azure后Spring Boot+React应用OAuth2登录及CORS问题排查求助

问题背景

前端部署为Azure Static Web App,后端Spring Boot部署为Azure Web App。本地测试所有功能正常,但部署到Azure后登录相关功能异常:

  • 应用核心功能(产品添加至订单、查看/编辑订单)需认证,点击添加产品按钮可正常跳转至Google OAuth2登录页面
  • 选择账号登录后,无法查看订单,系统持续要求登录
  • 直接在浏览器中访问后端已部署的/api/clients/me/端点,能正常显示刚登录的用户信息;但前端通过axios发起相同请求时,触发CORS错误

具体错误信息

Access to XMLHttpRequest at 'https://backendurl.azurewebsites.net/api/clients/me/' from origin 'https://mango.5.azurestaticapps.net' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.

已尝试方案

  • 临时禁用后端CSRF防护,问题仍未解决

相关代码

后端SecurityConfig.java

package com.xavier.client_backend.config;

import com.xavier.client_backend.domain.entities.ClientEntity;
import com.xavier.client_backend.repositories.ClientRepository;
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.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final ClientRepository clientRepository;

    public SecurityConfig(ClientRepository clientRepository) {
        this.clientRepository = clientRepository;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -> {
                CorsConfigurationSource source = request -> {
                    CorsConfiguration configuration = new CorsConfiguration();
                    configuration.setAllowedOrigins(List.of("<redacted_url>"));
                    configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
                    configuration.setAllowedHeaders(List.of("*"));
                    configuration.setAllowCredentials(true);
                    return configuration;
                };
                cors.configurationSource(source);
            })
            .authorizeHttpRequests(auth -> {
                auth.requestMatchers("/api/products/**").permitAll();
                auth.anyRequest().authenticated();
            })
            .oauth2Login(oauth2 -> oauth2.successHandler(successHandler()))
            .logout(logout -> logout
                    .logoutUrl("/logout")
                    .invalidateHttpSession(true)
                    .clearAuthentication(true)
                    .deleteCookies("JSESSIONID")
                    .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
            );
        return http.build();
    }

    @Bean
    public AuthenticationSuccessHandler successHandler() {
        return (request, response, authentication) -> {
            OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
            String email = oAuth2User.getAttribute("email");

            ClientEntity client = clientRepository.findByEmail(email);
            if (client == null) {
                client = new ClientEntity();
                client.setName(oAuth2User.getAttribute("name"));
                client.setEmail(email);
                clientRepository.save(client);
            }

            SimpleUrlAuthenticationSuccessHandler handler = new SimpleUrlAuthenticationSuccessHandler();
            handler.setDefaultTargetUrl("<redacted_url>");
            handler.onAuthenticationSuccess(request, response, authentication);
        };
    }
}

前端ProductList.js

import React, { useEffect, useState } from "react";
import axios from "axios";

const ProductList = ({ isAuthenticated }) => {
    const [products, setProducts] = useState([]);
    const [client, setClient] = useState(null);
    const [showToast, setShowToast] = useState(false);
    const [toastMessage, setToastMessage] = useState("");

    useEffect(() => {
        const fetchClientAndProducts = async () => {
            try {
                const clientResponse = await axios.get(
                    `https://backendurl.azurewebsites.net/api/clients/me/`,
                    { withCredentials: true }
                );
                if (clientResponse.status === 200) {
                    setClient(clientResponse.data);
                    const productsResponse = await axios.get(
                        `https://backendurl.azurewebsites.net/api/products`
                    );
                    const productsData = await productsResponse.data;
                    setProducts(productsData);
                } else {
                    setClient(null);
                }
            } catch (error) {
                console.error("Error fetching client or products:", error);
                setClient(null);
            }
        };
        fetchClientAndProducts();
    }, []);

    const handleAddToOrder = async (productId, productName) => {
        if (!isAuthenticated) {
            window.location.href =
                "https://backendurl.azurewebsites.net/oauth2/authorization/google";

            return;
        }

        try {
            // Check if there is an existing order for the client
            const ordersResponse = await axios.get(
                `https://backendurl.azurewebsites.net/api/orders/client/${client.id}`,
                { withCredentials: true }
            );
            const orders = ordersResponse.data;

            if (orders.length > 0) {
                // There is an existing order, update it
                const existingOrder = orders[0]; // Assuming only one open order at a time
                const updatedOrderItems = [...existingOrder.orderItems];
                const existingItem = updatedOrderItems.find(
                    (item) => item.productId === productId
                );

                if (existingItem) {
                    existingItem.quantity += 1;
                } else {
                    updatedOrderItems.push({ productId, quantity: 1 });
                }

                const updatedOrder = {
                    ...existingOrder,
                    orderItems: updatedOrderItems,
                };

                await axios.put(
                    `https://backendurl.azurewebsites.net/api/orders/${existingOrder.id}`,
                    updatedOrder,
                    { withCredentials: true }
                );
                setToastMessage(`${productName} added to existing order`);
                setShowToast(true);
                setTimeout(() => setShowToast(false), 3000); // Hide toast after 3 seconds
            } else {
                // No existing order, create a new one
                const newOrder = {
                    clientId: client.id,
                    orderDate: new Date().toISOString(),
                    orderItems: [{ productId, quantity: 1 }],
                };

                const response = await axios.post(
                    `https://backendurl.azurewebsites.net/me/api/orders`,
                    newOrder,
                    {
                        withCredentials: true,
                    }
                );
                if (response.status === 201) {
                    setToastMessage(`${productName} added to new order`);
                    setShowToast(true);
                    setTimeout(() => setShowToast(false), 3000); // Hide toast after 3 seconds
                } else {
                    console.error("Failed to add product to new order");
                }
            }
        } catch (error) {
            console.error("Error adding product to order:", error);
        }
    };

    return (
        <div className="container mt-4">
            <div className="row">
                {products.map((product) => (
                    <div className="col-md-3 mb-4" key={product.id}>
                        <div className="card h-100 text-center">
                            <div className="card-body">
                                <h5 className="card-title">{product.name}</h5>
                                <p className="card-text">Price: R{product.price.toFixed(2)}</p>
                                <button
                                    className="btn btn-primary"
                                    onClick={() => handleAddToOrder(product.id, product.name)}
                                >
                                    Add to Order
                                </button>
                            </div>
                        </div>
                    </div>
                ))}
            </div>
            <div
                className="toast-container position-fixed bottom-0 end-0 p-3"
                style={{ zIndex: 9999 }}
            >
                <div
                    className={`toast ${showToast ? "show" : "hide"}`}
                    role="alert"
                    aria-live="assertive"
                    aria-atomic="true"
                >
                    <div className="toast-header">
                        <strong className="me-auto">Notification</strong>
                        <button
                            type="button"
                            className="btn-close"
                            onClick={() => setShowToast(false)}
                        ></button>
                    </div>
                    <div className="toast-body">{toastMessage}</div>
                </div>
            </div>
        </div>
    );
};

export default ProductList;

烦请帮忙排查问题,感谢!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 19:32:01