Angular访问Spring Boot授权服务器持续出现CORS错误
我来帮你一步步拆解并解决这个问题,你现在遇到了两个核心问题:OPTIONS请求返回404,以及浏览器的CORS跨域限制。咱们先从404的根源开始排查,再处理CORS的配置:
第一步:先搞定OPTIONS请求404的问题
浏览器发送OPTIONS预检请求找不到路径,说明你的Spring Boot OAuth服务器要么没正确配置/oauth/authorize端点,要么这个路径被拦截了,甚至可能你用错了请求方法。
确认授权端点是否正确配置
检查你的AuthorizationServer配置类,确保/oauth/authorize端点已经正确启用。比如:@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // 这里要确保配置了认证管理器、令牌存储等核心组件,端点才会生效 endpoints.authenticationManager(authenticationManager) .tokenStore(tokenStore); } }检查Spring Security是否拦截了端点
你的WebSecurity配置不能把/oauth/authorize给拦截了,至少要允许匿名访问(或者根据你的业务需求配置权限):@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth/authorize").permitAll() // 放开授权端点的访问限制 .anyRequest().authenticated(); } }注意请求方法的兼容性
划重点:/oauth/authorize端点默认是支持GET请求的,如果你用POST请求,得确认你的授权服务器是否允许POST方式的授权请求。比如授权码模式通常可以用POST,但有些配置下可能只开了GET。可以先尝试用GET请求测试这个端点是否能正常访问,排除路径本身的问题。
第二步:处理CORS跨域限制
解决了404之后,还得处理浏览器的CORS预检问题。这里要注意:Spring Security的过滤器优先级很高,普通的@CrossOrigin注解或者WebMvc的CORS配置可能不生效,必须在Spring Security层面配置CORS。
方法1:全局配置CORS(推荐)
在你的WebSecurity配置类里,直接启用CORS并配置允许的源、方法和头:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 一定要把cors()放在前面,确保CORS过滤器先执行 http.cors().and() .authorizeRequests() .antMatchers("/oauth/authorize").permitAll() .anyRequest().authenticated(); } // 定义CORS的具体规则 @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfig = new CorsConfiguration(); // 允许你的Angular应用域名 corsConfig.setAllowedOrigins(Arrays.asList("http://localhost:4200")); // 允许的请求方法,必须包含OPTIONS预检请求 corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS")); // 允许的请求头,根据你的需求调整 corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); // 如果需要携带Cookie(比如会话认证),开启这个 corsConfig.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 对所有路径应用这个CORS规则,也可以只针对/oauth/**配置 source.registerCorsConfiguration("/**", corsConfig); return source; } }
方法2:针对OAuth端点单独配置CORS
如果全局配置不符合你的需求,也可以单独给OAuth的端点配置CORS,在AuthorizationServer配置类里添加:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients() // 给OAuth端点绑定CORS配置 .corsConfigurationSource(corsConfigurationSource()); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowedOrigins(Arrays.asList("http://localhost:4200")); corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS")); corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); corsConfig.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/oauth/**", corsConfig); return source; } }
第三步:Angular端的请求也要配到位
确保你的Angular请求设置了正确的请求头,并且如果需要携带凭证的话开启withCredentials:
import { HttpClient, HttpHeaders } from '@angular/common/http'; // 比如在你的授权服务中 constructor(private http: HttpClient) {} getAuthToken() { // OAuth授权请求通常用application/x-www-form-urlencoded格式 const headers = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }); // 构造请求参数,根据你的授权模式调整(比如授权码模式、密码模式) const bodyParams = new URLSearchParams(); bodyParams.set('client_id', '你的客户端ID'); bodyParams.set('response_type', 'code'); // 或者token,根据模式来 bodyParams.set('redirect_uri', 'http://localhost:4200/auth-callback'); bodyParams.set('scope', 'read write'); // 你的权限范围 return this.http.post('http://localhost:9999/oauth/authorize', bodyParams.toString(), { headers: headers, withCredentials: true // 如果需要携带Cookie(比如用户登录会话),一定要开这个 }); }
最后提醒一句:如果是授权码模式,其实更推荐让浏览器直接跳转到/oauth/authorize端点,让用户完成登录授权后再回调到Angular应用,而不是用AJAX请求。AJAX请求更适合密码模式、客户端凭证模式这类不需要用户交互的场景。
内容的提问来源于stack exchange,提问作者tm1701

