我的 API 服务器在 Lumen 框架上运行,并且在那里安装了 CORS 中间件,以及管理 JSON POST 数据的中间件。在服务器端,一切都很完美。
现在,对于 UI,我使用 Angular 9 和 Material 布局。我有一个非常简单的登录组件:它基本上验证输入,然后调用与我的 API 服务器通信的服务AuthService。
AuthService 非常简单:
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpParams } from '@angular/common/http';
export interface Token {
token: string;
token_type: string;
expires_in: number;
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
public isAuthenticated = new BehaviorSubject<boolean>(false);
constructor(
private http: HttpClient,
private router: Router
) {}
async checkAuthenticated() {
const authToken = localStorage.getItem('access_token');
return (authToken !== null);
}
getToken() {
return localStorage.getItem('access_token');
}
async login(username: string, password: string) {
const api = 'http://api.elektron.test/v1/login';
const headers = new HttpHeaders({'Content-Type': 'application/json; charset=UTF-8'});
const body = JSON.stringify({
email: username,
password
});
// tslint:disable-next-line:max-line-length
return this.http.post(api, body)
.subscribe((res: any) => {
if(res.token) {
this.isAuthenticated.next(true);
localStorage.setItem('access_token', res.token);
}
}, error => {
console.error(error);
}, () => {
console.log('etstamente');
});
}
async logout(redirect: string) {
const removeToken = localStorage.removeItem('access_token');
if (removeToken == null) {
this.isAuthenticated.next(false);
this.router.navigate([redirect]);
}
}
handleError(error: HttpErrorResponse) {
let msg = '';
if (error.error instanceof ErrorEvent) {
// client-side error
msg = error.error.message;
} else {
// server-side error
msg = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(msg);
}
}
在 this.http.post
中,我可以在第三个参数中传递附加 header ,并且我已经尝试过这样做,但是我面临的问题是,无论我传递给它什么 header ,并且当我调用该请求时,Content-Type
永远不会发送。
我尝试的另一个想法是使用拦截器:
import {HttpInterceptor, HttpRequest, HttpHandler, HttpHeaders} from '@angular/common/http';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = this.authService.getToken();
req = req.clone({
responseType: 'json'
});
if (token) {
req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
}
if (!req.headers.has('Content-Type')) {
req = req.clone({
setHeaders: {
'Content-Type': 'application/json',
}
});
}
// setting the accept header
req = req.clone({ headers: req.headers.set('Accept', 'application/json') });
return next.handle(req);
}
}
当它到达那里的任何 req.clone
语句时,我最终会得到与之前针对 POST
请求所解释的行为相同的行为。
所以,我不知道我在这里做错了什么或者是什么导致了这个。在 Chrome 浏览器中,在网络选项卡下的控制台中,当我尝试查看请求 header 时,当应用上面的这些情况时,它会显示显示临时 header - 我找到了一些 Stack Overflow 答案在这个问题上,但他们都没有解决我的问题。
我花了过去 5-6 个小时在网上搜索解决方案,尝试了一些我的想法,但都无济于事。
编辑:此问题与 Angular 无关,而是与服务器和后端配置以及处理预检请求有关。
最佳答案
经过一个不眠之夜,我终于解决了这个问题,这不是 Angular 或我的代码的问题,而是 Web 服务器的配置问题。
如果有人经历过类似的事情,这个答案可能会提供一个潜在的解决方案,这是 preflight request 的书籍示例。 :
A CORS preflight request is a CORS request that checks to see if the CORS protocol is understood and a server is aware using specific methods and headers.
It is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method, Access-Control-Request-Headers, and the Origin header.
这意味着在 Angular 应用发出每个请求之前,都会有另一个请求通过 OPTIONS
请求检查服务器的 CORS 选项。
我的服务器设置和 Lumen/Laravel CORS 设置一直失败。这就是我的 CorsMiddleware
从一开始的样子:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
/**
* Class CorsMiddleware
* @package App\Http\Middleware
*/
class CorsMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$headers = [
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => '86400',
'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS, PUT, PATCH, HEAD, DELETE',
'Access-Control-Allow-Headers' => 'X-Requested-With, Content-Type, Accept, Origin, Authorization',
'Access-Control-Expose-Headers' => 'X-Requested-With, Content-Type, Accept, Origin, Authorization',
'Access-Control-Allow-Origin' => '*',
];
if ($request->isMethod('OPTIONS')) {
return response()->json(['method' => 'OPTIONS'], 200, $headers);
}
$response = $next($request);
foreach($headers as $key => $value) {
$response->header($key, $value);
}
return $response;
}
}
然后这个中间件被正确注入(inject)到 bootstrap/app.php
中:
$app->routeMiddleware([
'cors' => App\Http\Middleware\CorsMiddleware::class,
...,
]);
当这个中间件运行时,所有 header 都在请求响应中发送,但是,CorsMiddleware
中的此语句从未达到此条件,即使我尝试使用 Postman:
if ($request->isMethod('OPTIONS')) {
return response()->json(['method' => 'OPTIONS'], 200, $headers);
}
原因是routes/web.php
没有处理任何OPTIONS
请求,所以这是由网络服务器本身处理的,因为我将 Apache 与 .htaccess
一起使用,这也导致了一些小问题,但我不想坚持让 Apache 处理这个问题。我想保持一个约定,即大多数事情都是由 Lumen/Laravel 处理的。
因此,为了让我的 CorsMiddleware
捕获 OPTIONS
请求,我通过将其添加到我的主 routes/web.xml 之上来解决整个问题。 php
文件:
Route::options('/{any:.*}', ['middleware' => ['cors']]);
现在,CorsMiddleware
中前面提到的 OPTIONS
案例从未发生过,是怎么发生的,这解决了我的整个问题。
关于 Angular 9 : Unable to change or modify HTTP headers,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60824263/