Angular使用Basic Authorization获取token时预检请求返回401错误
嘿,这个问题我之前也碰到过!核心原因是浏览器的跨域预检机制在搞事情:当你发送带自定义头(比如Authorization)的跨域请求时,浏览器会自动先发一个OPTIONS类型的预检请求,确认服务器是否允许这个跨域操作。如果服务器对OPTIONS请求也要求Basic Auth验证,就会返回401,浏览器直接就把后续的POST请求拦下来了——而Postman不会自动发预检请求,所以能正常拿到token。
下面我帮你调整代码,同时告诉你服务器端必须做的配置,双管齐下解决问题:
一、优化Angular请求代码
你的代码有几个可以规范的地方,调整后更符合OAuth2的请求规范:
- HttpHeaders是不可变对象,直接链式创建更简洁
- 把grant_type、username、password放到请求体里(而不是URL参数),这是OAuth2密码模式的标准做法
- 确保请求体是
application/x-www-form-urlencoded格式的字符串
修改后的服务代码:
requestToken() { const authUsername = 'my-username'; const authPassword = 'my-password'; // 编码Basic Auth凭证 const encodedCredentials = btoa(`${authUsername}:${authPassword}`); // 构建请求体,转为x-www-form-urlencoded格式 const requestBody = new URLSearchParams(); requestBody.set('grant_type', 'password'); requestBody.set('username', authUsername); requestBody.set('password', authPassword); // 链式创建请求头 const requestHeaders = new HttpHeaders() .set('Content-Type', 'application/x-www-form-urlencoded') .set('Authorization', `Basic ${encodedCredentials}`); return this._http.post( `${API_URL}/oauth/token`, requestBody.toString(), // 转成符合格式的字符串 { headers: requestHeaders, withCredentials: true } ); }
组件调用可以保留,建议加上详细错误打印方便调试:
this._dataService.requestToken() .subscribe( res => { sessionStorage.setItem('access_token', res['access_token']); sessionStorage.setItem('refresh_token', res['refresh_token']); }, err => { console.error('Authentication failed! 详细错误:', err); // 打印完整错误信息 } );
二、服务器端关键配置(重中之重!)
这一步是解决预检401的核心,你必须让服务器对OPTIONS请求跳过Basic Auth验证,同时返回正确的CORS响应头:
如果后端是Spring Boot(常见的OAuth2后端)
可以在Security配置里放行OPTIONS请求,同时配置CORS:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 先放行OPTIONS预检请求,不做Auth验证 .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll() .anyRequest().authenticated() .and() // 开启Basic Auth .httpBasic(); } // 配置CORS允许跨域请求 @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowCredentials(true); // 替换成你的前端域名,比如http://localhost:4200 corsConfig.addAllowedOrigin("http://localhost:4200"); corsConfig.addAllowedHeader("*"); corsConfig.addAllowedMethod("*"); source.registerCorsConfiguration("/**", corsConfig); return new CorsFilter(source); } }
如果用Nginx做反向代理
在Nginx配置里添加对OPTIONS请求的特殊处理:
location /oauth/token { # 处理OPTIONS预检请求 if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin "你的前端域名"; add_header Access-Control-Allow-Methods "POST, OPTIONS"; add_header Access-Control-Allow-Headers "Authorization, Content-Type"; add_header Access-Control-Allow-Credentials "true"; return 204; # 返回204空响应即可,无需内容 } # 其他代理配置 proxy_pass http://你的后端服务地址; }
做完这两步,浏览器的预检OPTIONS请求就能被服务器正常响应(返回204),不会触发401,后续的POST请求就能顺利拿到access_token啦!
内容的提问来源于stack exchange,提问作者raf
相关产品推荐
相关产品推荐

