SpringBoot+Angular跨域异常排查:已配置@CrossOrigin仍报CORS错误
环境与配置
后端(Java SpringBoot)
控制器代码
@CrossOrigin(origins = "*", maxAge = 3600)//allow cross-origin resource sharing (CORS) request @RestController @RequestMapping("/api/school") public class CourseController { @PostMapping("/login") public ResponseEntity<?> loginPage(@RequestBody LoginRequest loginRequest){ ***** Authentication authentication= authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(name, passowrd)); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = (UserDetails)authentication.getPrincipal(); LoginResponse loginResponse =new LoginResponse(); loginResponse.setUsername(userDetails.getUsername()); return ResponseEntity.ok(loginResponse); } @GetMapping("/apartment/{apartmentName}") public ResponseEntity<?> findTeachers(@PathVariable("apartmentName")String apartName){ ****** return ResponseEntity.ok(list); } }
WebSecurity配置
@Configuration @EnableWebSecurity //it's allow the static resource, image, css, js.. @EnableGlobalMethodSecurity(prePostEnabled = true) //allow the multiple endpoint public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors() .and().csrf().disable() .authorizeHttpRequests() .antMatchers("/api/school/login").permitAll() // login do not need to authenticated .anyRequest().authenticated() .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().httpBasic(); } }
前端(Angular)
服务代码
export class CourseService { private baseUrl = 'http://localhost:9010/api/school'; constructor(private _httpClient: HttpClient) {} login(request: LoginRequest): Observable<LoginResponse> { return this._httpClient .post<LoginResponse>(this.baseUrl + '/login', request) .pipe(catchError(this.errorHandler)); } getAllTeachers(apartmentName: string): Observable<TeachersResponse[]> { return this._httpClient .get<TeachersResponse[]>(this.baseUrl + '/apartment/' + apartmentName) .pipe(catchError(this.errorHandler)); } }
组件调用代码
loadData(apartmentName: string) { console.log('call courseService'); this._courseService.getAllTeachers(apartmentName).subscribe((teachers) => { this.teacherArray = teachers; console.log(teachers); }); }
问题描述
登录接口可正常访问,跳转至课程页面后,调用loadData方法获取教师列表时触发CORS错误:
origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
已尝试以下配置但均无效:
- 控制器类上添加
@CrossOrigin(origins = "*", maxAge = 3600)或指定具体 origin - 给控制器方法单独添加
@CrossOrigin - WebSecurity配置中开启
http.cors()
最终在SecurityConfig类中添加自定义CorsConfigurationSource方法后问题解决,想知道原有配置失效的原因。
原因分析
Spring Security的CORS优先级高于控制器注解
当你在WebSecurity中调用http.cors()时,Spring Security会启用自己的CORS过滤器,这个过滤器会优先处理跨域请求,直接忽略控制器上的@CrossOrigin注解。而默认情况下,Spring Security的CORS配置非常严格,几乎不允许任何跨域请求,所以受保护接口的跨域验证直接失败。登录接口正常的特殊原因
登录接口/api/school/login被设置为permitAll(),意味着该请求会绕过Spring Security的认证过滤器,同时也会跳过Spring Security的CORS过滤器。此时控制器上的@CrossOrigin注解才会生效,所以登录请求没有出现CORS问题。而获取教师列表的接口需要认证,请求必须经过Spring Security的CORS过滤器,因无有效配置导致跨域失败。自定义
CorsConfigurationSource生效的逻辑
当你定义了CorsConfigurationSourceBean后,Spring Security的CORS过滤器会直接使用这个配置处理跨域请求,覆盖默认的严格规则,从而允许你的前端域名访问受保护接口。
补充方案
如果不想自定义CorsConfigurationSource,也可以通过以下方式让控制器注解生效,但不推荐这种方式(会增加受保护接口的安全风险):
在WebSecurity配置中禁用Spring Security的CORS过滤器:
http.cors().disable()
这种方式仅适合所有接口都通过控制器注解配置CORS的场景,无法统一管理跨域规则。更推荐的做法是通过CorsConfigurationSource配置全局CORS规则,既能配合Spring Security的认证机制,又能统一管控跨域策略。
内容的提问来源于stack exchange,提问作者D999_X

