Ошибка 400 при запросе к API контроллеру из вне на IIS, на IISExpress ошибка не возникает, при внутреннем запросе (localhost) ошибки тоже нет
Проект ASP NET CORE MVC 3. Сервер IIS10.
Представления изначально отображаются с помощью контроллеров mvc. Везде где задействованы mvc контроллеры - все работает и на IISExpress и на IIS после деплоя. Часть функционала у меня работает через api-контроллеры, функции JS у меня отправляют запрос на api-контроллеры, и вот API контроллеры после публикации не работают, если запрос идет из вне - выдает ошибку:
Failed to load resource: the server responded with a status of 400 (Bad Request)
{type: "https://tools.ietf.org/html/rfc7231#section-6.5.1", title: "Bad Request", status: 400,…}
status: 400
title: "Bad Request"
traceId: "|1763bd64-457e0bbd74a0ee83."
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1"
В заголовках запроса и ответа:
General
Request URL: http://195.226.ххх.хх/CollectVoters/api/API/searchStreets
Request Method: POST
Status Code: 400 Bad Request
Remote Address: 195.226.ххх.хх:80
Referrer Policy: strict-origin-when-cross-origin
Content-Length: 135
Content-Type: application/problem+json; charset=utf-8
Date: Sun, 25 Jul 2021 11:42:34 GMT
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Response
Content-Length: 135
Content-Type: application/problem+json; charset=utf-8
Date: Sun, 25 Jul 2021 11:42:34 GMT
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Request
Accept: application/json
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 43
Content-Type: application/json
Host: 195.226.ххх.хх
Origin: http://195.226.ххх.хх
Pragma: no-cache
Referer: http://195.226.ххх.хх/collectvoters/Friends/Create
RequestVerificationToken: CfDJ8E_6DbN7jdpPgMFyLYN6J0P0VzaS58Os_XphjMs6pjfQCxYf7ONglfuOTkhJe0fAG-BL6yle7KQOFJ46hExJe61mPCI3u0Ad9VLa1dVwE4p4A55xXzZ10llou70siWF3WBoS5WOyL9ZnXnqfb0QM_OkTbgsFOtGdNp-MWCCPmgAerO9sa7Nj7QBO2OdjlKONdg
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36
X-Requested-With: XMLHttpRequest
{IdCity: 10, Name: "Сакмарский"}
IdCity: 10
Name: "Сакмарский"
API контроллер обрабатывающий запрос:
[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
public class APIController : ControllerBase
{
private readonly VoterCollectorContext _context;
public APIController(VoterCollectorContext context)
{
_context = context;
}
[HttpPost("searchStreets")]
[ValidateAntiForgeryToken]
public IActionResult SearchStreets(CityDTO citySelected)
{
List<Street> streets = _context.Street.Where(s => s.CityId == citySelected.CityId).ToList<Street>();
if (streets.Any())
{
//...
return Ok(streetsDTO);
}
return NoContent();
}
}
Функция JS осуществляющая ajax запрос:
// Обновление списка улиц после выбора города
$(function () {
$("#CityId").change(function () {
var formData = { 'CityId': Number.parseInt($('#CityId').val()), 'Name': $('#CityId>option:selected').text() };
$.ajax({
// url: "http://localhost:18246/api/API/searchStreets",
url: "/api/API/searchStreets",
headers:
{
'Accept': 'application/json',
'Content-Type': 'application/json',
'RequestVerificationToken': $('#RequestVerificationToken').val()
},
type: 'POST',
dataType: "json",
data: JSON.stringify(formData),
success: function (data) {
dataFilling(data, 'idStreet', 'name', '#StreetId', '<option/>');
// Генерация события для элемента Select
let elemSelectHouse = document.querySelector('#StreetId')
elemSelectHouse.selectedIndex = 0;
const event = new Event("change");
elemSelectHouse.dispatchEvent(event);
},
error: function (result, status, er) {
alert("error: " + result + " status: " + status + " er:" + er);
}
});
});
});
С Самого компа, где стоит IIS запросы уходили нормально (потом запустил на нём IIS Express и полетела авторизация).
Изначально он ещё выдавал ошибку:
POST http://localhost/api/API/searchStreets 404 (Not Found) jquery.min.js:2
Её удалось решить, для запросов на api-контроллеры в url необходимо ещё было добавлять адрес самого приложения, т.е. указывать не /api/API/searchStreets, как на IISExpress, а /CollectVoters/api/API/searchStreets.
По советам в интернете пробовал чистить куки, отключать брендмауер, отключать антивирусник - не помогло.
Подскажите пожалуйста, в чем ошибка. Что нужно посмотреть?
Может ли это быть связано с CORS? С одной стороны запрос простой POST, но с другой стороны на лицо доступ к другому домену, а значит «Принцип одинакового источника», который позволяет беспрепятственно получать доступ к контенту и методам уже не работает. Тогда при запросе перед отправкой самого POST-запроса должен отправляться OPTIONS-запрос содержащий заголовки Access-Control-Request-Method и Access-Control-Request-Headers, а сервер должен дать ответ с заголовками - Access-Control-Allow-Methods должен содержать разрешённые методы, Access-Control-Allow-Headers должен содержать список разрешённых заголовков. У меня запрос OPTIONS не отправляется. Есть ещё момент, что вроде OPTIONS-запрос при простых запросах не нужен, но в ответе от сервера другого домена должен быть заголовок Access-Control-Allow-Origin, который должен содержать разрешённый источник, по идее тот, что в заголовке Origin (в моем случае http://195.226.ххх.хх), но в заголовке Origin у меня домен моего сервера, и уже получается, что это не междоменный запрос и источник==сервер. Вот тут я уже запутался. Подскажите, что тут не так?
Причина была в аннотации методов у api-контроллеров
[ValidateAntiForgeryToken]
Не понятно почему она не отрабатывала как нужно? На страницы был метод формирующий AntiForgeryToken и скрытое поле, в котором он хранился, mvc-контроллеры которые грузили и обновляли страницу и которых также была аннотация ValidateAntiForgeryToken, принимали данный AntiForgeryToken:
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions {
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<h4>Избиратель</h4>
<hr />
<div class="row">
<input type="hidden" id="RequestVerificationToken"
name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">
<div class="col-md-4">
...
В методе JS отправляющим запрос на сервер этот AntiForgeryToken брался и записывался в заголовок запроса и если смотреть заголовки Request он там присутствует: автор
Request
Accept: application/json
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 43
Content-Type: application/json
Host: 195.226.ххх.хх
Origin: http://195.226.ххх.хх
Pragma: no-cache
Referer: http://195.226.ххх.хх/collectvoters/Friends/Create
RequestVerificationToken: CfDJ8E_6DbN7jdpPgMFyLYN6J0P0VzaS58Os_XphjMs6pjfQCxYf7ONglfuOTkhJe0fAG-BL6yle7KQOFJ46hExJe61mPCI3u0Ad9VLa1dVwE4p4A55xXzZ10llou70siWF3WBoS5WOyL9ZnXnqfb0QM_OkTbgsFOtGdNp-MWCCPmgAerO9sa7Nj7QBO2OdjlKONdg
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36
X-Requested-With: XMLHttpRequest