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

Spring+Angular跨域CORS问题排查与解决求助

Spring REST接口CORS问题排查与解决

问题描述

我用Spring搭建了一个简易REST应用,Angular调用API http://localhost:9595/api/luoghi/all 时出现CORS错误:

Access to XMLHttpRequest at 'http://localhost:9595/api/luoghi/all' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

已经尝试在Rest Controller添加@CrossOrigin注解、配置WebConfig类,但问题仍未解决,附上相关代码文件,请问遗漏了什么?

相关代码

Rest Controller

package it.ootareg.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import it.ootareg.entities.Luoghi;
import it.ootareg.services.LuoghiService;
import jakarta.transaction.Transactional;

@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/luoghi")
@Transactional
public class LuoghiController {

    @Autowired
    private LuoghiService lService;
    
    @GetMapping("/all")
    public ResponseEntity<List<Luoghi>> getAll(){
        
        List<Luoghi> luoghi = lService.findAll();
        
        return new ResponseEntity<>(luoghi, HttpStatus.OK);
    }
    
    @GetMapping("/find/{id}")
    public ResponseEntity<Luoghi> getById(@PathVariable("id") Long id){
        
        Luoghi luoghi = lService.findLuoghiById(id );
        
        return new ResponseEntity<>(luoghi, HttpStatus.OK); 
    }
    
    @PostMapping("/add")
    public ResponseEntity<Luoghi> add(@RequestBody Luoghi luogo){
        
        Luoghi newLuogo = lService.addLuoghi(luogo);
        
        return new ResponseEntity<>(newLuogo, HttpStatus.CREATED); 
    } 
    
    @PutMapping("/update")
    public ResponseEntity<Luoghi> update(@RequestBody Luoghi luogo){
        
        Luoghi newLuogo = lService.updateLuoghi(luogo);
        
        return new ResponseEntity<>(newLuogo, HttpStatus.OK); 
    }
     
    @GetMapping("/delete/{id}")
    public ResponseEntity<?> delete (@PathVariable("id") Long id){
        
        lService.deleteLuoghi(id);
        
        return new ResponseEntity<>(HttpStatus.OK); 
    }
    
}

Web Config

package it.ootareg.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:4200")
                .allowedMethods(HttpMethod.GET.name(),
                        HttpMethod.POST.name(),
                        HttpMethod.PUT.name(),
                        HttpMethod.DELETE.name())
                .allowedHeaders(HttpHeaders.CONTENT_TYPE,
                        HttpHeaders.AUTHORIZATION);
    }
}

Rest Service

package it.ootareg.services;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import it.ootareg.entities.Luoghi;
import it.ootareg.exceptions.UserNotFoundException;
import it.ootareg.repo.LuoghiRepo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Service
public class LuoghiService {

    @Autowired
    private LuoghiRepo repo;
    
    public Luoghi addLuoghi(Luoghi luogo) {
        
        return repo.save(luogo);
    }
    
    public List<Luoghi> findAll(){
        
        return repo.findAll();
    }
    
    public Luoghi updateLuoghi(Luoghi luogo) {
        
        return repo.save(luogo);
    }
    
    public void deleteLuoghi(Long id) {
        
         repo.deleteLuoghiById(id);
    }
    
    public Luoghi findLuoghiById(Long id) {
        
        return repo.findLuoghiById(id).orElseThrow(
                ()->new UserNotFoundException("Luogo " + id +" non trovato"));
    }
    
}

Angular Service

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Luoghi } from './Luoghi';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class LuoghiService {

  constructor(private http: HttpClient) { }

  private apiUrl = "http://localhost:9595/api";

  public getLuoghi() : Observable<Luoghi[]> {
    return this.http.get<Luoghi[]>(`${this.apiUrl}/luoghi/all`)
  }

  public addLuogo(luogo : Luoghi) : Observable<Luoghi> {
    return this.http.post<Luoghi>(`${this.apiUrl}/luoghi/add`, luogo)
  }

  public updateLuogo(luogo : Luoghi) : Observable<Luoghi> {
    return this.http.put<Luoghi>(`${this.apiUrl}/luoghi/update`, luogo)
  }

  public deleteLuogo(luogoID : number) : Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/luoghi/delete/${luogoID}`)
  }
   
}

App Module

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LuoghiService } from './luoghi.service';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [LuoghiService],
  bootstrap: [AppComponent]
})
export class AppModule { }

排查与解决方向

1. 路径前缀与CORS匹配问题

Angular请求路径是/api/luoghi/all,但Spring Controller的@RequestMapping是/luoghi,说明Spring端应该配置了全局路径前缀/api(比如通过application.properties里的server.servlet.context-path=/api)。虽然WebConfig里的addMapping("/**")理论上会覆盖所有路径,但需要确认是否有其他拦截器、过滤器在CORS处理之前执行,导致响应头未被正确添加。

2. 注解冲突问题

同时使用Controller上的@CrossOrigin和全局WebConfig的CORS配置可能引发冲突,建议只保留一种配置方式:要么删掉Controller上的@CrossOrigin专注全局配置,要么移除WebConfig只保留Controller级别的注解。

3. 预检请求(OPTIONS)未处理

浏览器会对非简单请求发送OPTIONS预检请求,你的WebConfig里的allowedMethods没有包含OPTIONS方法,这会导致预检失败。修改全局配置,添加OPTIONS方法:

.allowedMethods(HttpMethod.GET.name(),
                HttpMethod.POST.name(),
                HttpMethod.PUT.name(),
                HttpMethod.DELETE.name(),
                HttpMethod.OPTIONS.name())

或者直接用allowedMethods("*")快速测试是否是这个问题。

另外注意:你的删除接口用了@GetMapping("/delete/{id}"),但Angular里用的是http.delete,这会导致请求方法不匹配,浏览器同样会触发预检,并且实际请求会失败,建议把删除接口改成@DeleteMapping("/delete/{id}")。

4. 请求头覆盖问题

WebConfig里的allowedHeaders只允许Content-Type和Authorization,如果Angular请求中带有其他自定义头,会被拦截。可以先改成allowedHeaders("*")测试,确认没问题后再按需限制。

5. 改用CorsFilter配置

如果WebMvcConfigurer的配置不生效,尝试直接注册CorsFilter Bean,优先级更高:

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:4200");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

6. 安全框架拦截(如果有)

如果项目中使用了Spring Security,需要在Security配置中显式开启CORS:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
            // 其他安全配置
            .authorizeRequests()
            .anyRequest().permitAll();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 09:24:53