Команда, над которой я работаю, имеет пользовательский интерфейс и API на двух разных доменах x-ui.app.example.com и x-api.app.example.com .Очевидно, что эта архитектура не работает с традиционными средствами XSRF в Angular / .NET Core.
Традиционный способ, которым она была описана:
- Когда x-api получаетэто первый запрос, или когда x-ui вызывает определенную конечную точку, x-api сгенерирует куки-файл 'XSRF-TOKEN', описанный здесь: https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.2
- x-ui будет читать этот куки-файл на следующих данныхманипулирует запросами, такими как POST, и будет включать содержимое файла cookie XSRF-TOKEN в заголовок запроса: X-XSRF-TOKEN
Это не работает для меня, так какЕсть два разных субдомена, что означает, что пользовательский интерфейс не может читать куки, созданные субдоменом x-api.Я знаю, что правильно сделать, это свернуть домен в один домен ui / api, и тогда все будет работать волшебно, однако, из-за нехватки времени или из-за того, что я не могу этого сделать, яможно обойти это, добавив файл cookie XSRF-TOKEN в домен app.example.com:
context.Response.Cookies.Append("XSRF-TOKEN", token, new CookieOptions
{
HttpOnly = false,
IsEssential = true,
Secure = true,
SameSite = SameSiteMode.None,
Domain = "app.example.com"
});
. Это дает UI доступ к cookie, поскольку он находится в родительском домене.Однако я не хочу этого делать, поскольку это будет означать, что любые другие приложения, созданные на app.example.com, смогут также увидеть cookie, и это просто грубо.
Другой способ, которым я рассматривалдля этого нужно было отправить XSRF-TOKEN обратно в заголовок ответа от первоначального вызова, после чего пользовательский интерфейс будет отвечать за сохранение содержимого в виде файла cookie в своем собственном домене, а затем последующие запросы смогут использовать этот файл cookie.
Мой вопрос : это приемлемый способ выполнения XSRF?Есть ли какая-либо уязвимость, передавая обратно XSRF-TOKEN в заголовке ответа от API, а затем сохраняя его как cookie в пользовательском интерфейсе?
Это будет выглядеть примерно так:
.NET Core
app.Use(next => context =>
{
if (context.Request.Method == HttpMethods.Get)
{
var token = antiforgery.GetAndStoreTokens(context).RequestToken;
context.Response.Headers.Append("XSRF-TOKEN", token);
}
return next(context);
});
CorsPolicyBuilder corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.WithExposedHeaders(new string[]{ "XSRF-TOKEN" });
corsBuilder.WithOrigins("http://localhost:4200", "null", "x-ui.app.example.com");
corsBuilder.AllowCredentials();
services.AddCors(options => { options.AddPolicy("AllowAll", corsBuilder.Build()); });
и угловой перехватчик: (Обратите внимание, что я добавил бы здесь больше проверок, чтобы посмотреть, был ли уже установлен cookie, чтобы он не происходил при каждом отдельном GET-запросе,но чтобы понять смысл).
import {
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpEvent,
HttpXsrfTokenExtractor, HttpResponse
} from '@angular/common/http';
import { Observable } from 'rxjs';
import {Injectable} from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { tap } from 'rxjs/operators';
@Injectable()
export class HttpXsrfInterceptorService implements HttpInterceptor {
private cookieValue: string;
constructor(private tokenExtractor: HttpXsrfTokenExtractor, private cookieService: CookieService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.method === 'GET') {
return next.handle(req).pipe(
tap(evt => {
if (evt instanceof HttpResponse) {
console.log(evt.headers.get('XSRF-TOKEN'));
this.cookieService.set( 'XSRF-TOKEN', evt.headers.get('XSRF-TOKEN'), null, '/', null, false, 'Lax' );
this.cookieValue = this.cookieService.get('XSRF-TOKEN');
}
})
);
}
// We will just forward HEAD, and OPTIONS requests on as usual, without
// trying to append the X-XSRF-TOKEN header, since XSRF validation isn't done
// on these methods anyways.
if (req.method === 'HEAD' || req.method === 'OPTIONS' ) {
return next.handle(req);
}
let requestToForward = req;
const headerName = 'X-XSRF-TOKEN';
// Try to extract the 'XSRF-TOKEN' cookie from the request.
const token = this.tokenExtractor.getToken() as string;
// If we successfully retrieved a token, we will set a header called 'X-XSRF-TOKEN' which
// will be used for XSRF validation on the API.
if (token !== null && !req.headers.has(headerName)) {
requestToForward = req.clone({setHeaders: {'X-XSRF-TOKEN': token}, withCredentials: true });
}
return next.handle(requestToForward);
}
}