最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - CORS error when accessing back-end from external address - Stack Overflow

programmeradmin4浏览0评论

I am hosting a Next.js front-end and a Spring Boot back-end in Docker on the same server. The front-end runs on port 3001, and the back-end runs on port 8080.

Server IP (external address): 192.168.100.116
Front-end URL (inside the network): http://192.168.100.116:3001
Back-end API URL: http://192.168.100.116:8080/api/v1/auth/authenticate

When accessing the front-end from my PC (same WiFi network), I get this CORS error when trying to call the back-end API:

Access to XMLHttpRequest at 'http://192.168.100.116:8080/api/v1/auth/authenticate' from origin 'http://192.168.100.116:3001' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

✅ Calling API from localhost:3001 works.

❌ Calling API from 192.168.100.116:3001 fails with CORS error.

What additional steps should I take to properly configure CORS in Spring Boot and Docker networking to allow external front-end requests from 192.168.100.116:3001 to 192.168.100.116:8080?

Docker-compose file

version: "3.9"
services:
  postgres_db:
    restart: always
    image: postgres:16.4
    container_name: project_xxx_postgres_db
    environment:
      POSTGRES_USER: xxx
      POSTGRES_PASSWORD: xxx
      POSTGRES_DB: project-ai-reception-db
    ports:
      - "5431:5432"
    volumes:
      - project_xxx_container_data:/var/lib/postgresql/data
    networks:
      - project-ai-rec-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  backend:
    restart: always
    image: xxx/project-ai-reception-backend:0.0.122
    container_name: project_xxx_backend
    ports:
      - "8080:8080"
    networks:
      - project-ai-rec-network
    depends_on:
      postgres_db:
        condition: service_healthy
    volumes:
      - uploads:/app/uploads
      - backend_logs:/app/logs
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - SPRING_APPLICATION_JSON={"server.address":"0.0.0.0"}

  cms-frontend:
    restart: always
    image: xxx/project-ai-reception-cms:0.0.5
    container_name: cms-frontend
    ports:
      - "3001:3001"
    environment:
      - NEXT_PUBLIC_BACKEND_URL=http://192.168.100.116:8080 # Use service name instead of IP
      - NEXT_PUBLIC_XXX_VOLUME_MANAGER_URL=http://192.168.100.116:5000
    depends_on:
      - backend
      - XXX-volume-manager
    networks:
      - project-ai-rec-network

volumes:
  project_xxx_container_data:
  uploads:
  album:
  backend_logs:

networks:
  project-ai-rec-network:

Spring Boot project

package com.xxx.ai_reception_backend.config;

import lombok.RequiredArgsConstructor;
import .springframework.beans.factory.annotation.Value;
import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.http.HttpStatus;
import .springframework.security.authentication.AuthenticationProvider;
import .springframework.security.config.annotation.web.builders.HttpSecurity;
import .springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import .springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import .springframework.security.config.http.SessionCreationPolicy;
import .springframework.security.core.AuthenticationException;
import .springframework.security.web.AuthenticationEntryPoint;
import .springframework.security.web.SecurityFilterChain;
import .springframework.security.web.access.AccessDeniedHandler;
import .springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import .springframework.web.cors.CorsConfiguration;
import .springframework.web.cors.CorsConfigurationSource;
import .springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.time.LocalDateTime;
import java.util.Arrays;

import static .springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

//    @Value("${spring.web.cors.allowed-origin-patterns-cms}")
//    private String cms_frontend_url;
//
//    @Value("${spring.web.cors.allowed-origin-patterns-frontdoor}")
//    private String frontdoor_frontend_url;

    private final  JwtAuthenticationFilter jwtAuthFilter;
    private final AuthenticationProvider authenicationProvider;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)  // Updated way to disable CSRF
                .cors(cors -> cors.configurationSource(corsConfigurationSource())) // Apply CORS config
                //.cors(withDefaults()) // Ensure CORS is enabled
                .headers(headers -> headers
                        .addHeaderWriter((request, response) -> {
                            response.setHeader("Access-Control-Allow-Origin", "*");
                            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
                            response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
                            response.setHeader("Access-Control-Allow-Credentials", "true");
                        })
                )
                .authorizeHttpRequests(auth -> auth
                        // Permit Swagger and API docs access without authentication
                        .requestMatchers(
                                "/v3/api-docs/**",
                                "/swagger-ui/**",
                                "/swagger-ui.html",
                                "/images/**",
                                "/album/image/**",
                                "/api/v1/attendance/create",
                                "/api/v1/workpass/broadcast/admin",
                                "/api/v1/mqtt/**",
                                "/api/v1/demo"
                        ).permitAll()
                        // Define other public endpoints
                        .requestMatchers("/api/v1/auth/**").permitAll()
                        // Secure all other endpoints
                        .anyRequest().authenticated()
                )
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // Set session management policy to stateless
                )
                .authenticationProvider(authenicationProvider)  // Set the custom authentication provider
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)  // Add JWT filter before UsernamePasswordAuthenticationFilter
                .exceptionHandling(exceptions -> {
                    exceptions
                            .accessDeniedHandler(accessDeniedHandler())        // Handle 403 Forbidden
                            .authenticationEntryPoint(authenticationEntryPoint()); // Handle 401 Unauthorized
                });
        return http.build();
    }


    // Custom Access Denied Handler for 403 Forbidden
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (HttpServletRequest request, HttpServletResponse response, .springframework.security.access.AccessDeniedException ex) -> {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 FORBIDDEN
            response.setContentType("application/json");
            String jsonResponse = String.format(
                    "{\"message\": \"%s\", \"details\": %s, \"statusCode\": \"%d\", \"time\": \"%s\"}",
                    "You don't have permission to access this resource. "+ex.getMessage(),
                    "Access Denied",
                    HttpStatus.FORBIDDEN.value(),
                    LocalDateTime.now()
            );
            response.getWriter().write(jsonResponse);
        };
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) -> {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 UNAUTHORIZED
            response.setContentType("application/json");
            String jsonResponse = String.format(
                    "{\"message\": \"%s\", \"details\": %s, \"statusCode\": \"%d\", \"time\": \"%s\"}",
                    "Please login to access this resource. "+authException.getMessage(),
                    "Unauthorized",
                    HttpStatus.UNAUTHORIZED.value(),
                    LocalDateTime.now()
            );
            response.getWriter().write(jsonResponse);
        };
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
//        configuration.setAllowedOriginPatterns(Arrays.asList("*")); // Allow all origins
        configuration.setAllowedOrigins(Arrays.asList(
                "http://192.168.100.116:3001",
                "http://192.168.100.116:3000",
                "http://192.168.1.86:3000",
                "http://192.168.1.86:3001"
        ));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        configuration.setAllowCredentials(true); // Allow credentials like cookies

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}
发布评论

评论列表(0)

  1. 暂无评论