大家好,可以帮助我。
我已经连续坐了好几天了,我不知道如何让我的 Spring Boot 和 React 应用程序工作。
前端部署为 Azure 静态 Web 应用程序,Spring Boot 后端部署为 Azure Web 应用程序。
最初,当我在本地测试时一切正常,但一旦部署到 Azure,登录就不再起作用了。该应用程序的基本功能是一个门户,您可以在其中添加可以添加到订单的产品页面。将产品添加到订单以及查看/编辑订单都需要进行身份验证。应用程序的这一部分工作正常,我单击“添加到产品”按钮,它会将我带到 google auth 页面,我在其中选择要登录的帐户。重定向后,我无法查看订单,并且不断要求我登录...
但是,当我在浏览器中运行端点(部署的后端)以获取当前登录的用户时,它会显示我刚刚在部署的前端上选择的用户。前端中的相同请求给出以下内容:
从源 '' 访问 ''(从 '' 重定向)的 XMLHttpRequest 已被 CORS 策略阻止:请求的资源上不存在 'Access-Control-Allow-Origin' 标头。
我已经尝试了一切,甚至在后端的某个时刻禁用了 CSRF。当地发展的流程正是我所寻找的:
添加产品或查看/编辑订单需要身份验证。转到这些页面或单击按钮会将我带到 Google Auth 并登录。之后我可以将产品添加到订单中并编辑订单。我不知道该怎么办了:(
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);
};
}
}
编辑2:
这是我供这些调用参考的 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;
以下是浏览器中出现的错误:
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'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
mango.5.azurestaticapps.net/:1 Access to fetch 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'.
后端和前端 URL 已更改一些。谢谢您的帮助!
控制器中是否使用了@CrossOrigin注解?检查一次是否有效?