Spring+Angular跨域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

