Как работает заголовок Access-Control-Allow-Origin?

1155

Видимо, я совершенно не понял ее семантику. Я думал о чем-то вроде этого:

  1. КЛИЕНТСКОМ DOWNLOADS JavaScript код MyCode.js из http://siteA- начала координат .
  2. Заголовок ответа файла MyCode.js содержит Access-Control-Allow-Origin:,http://siteB что, как я думал, означало, что MyCode.js было разрешено делать ссылки из разных источников на сайт B.
  3. Клиент запускает некоторые функции MyCode.js, которые, в свою очередь, отправляют запросы http://siteB, что должно быть хорошо, несмотря на то, что они являются запросами разных источников.

Ну, я не прав. Это не работает, как это вообще. Итак, я прочитал разделение ресурсов между источниками и попытался прочитать разделение ресурсов между источниками в рекомендации w3c

Одно можно сказать наверняка - я все еще не понимаю, как я должен использовать этот заголовок.

У меня есть полный контроль над сайтом A и сайтом B. Как включить код JavaScript, загруженный с сайта A, для доступа к ресурсам на сайте B с помощью этого заголовка?

PS

Я не хочу использовать JSONP.

отметка
источник
3
Я не уверен, но я считаю , что установка заголовка таким образом позволяет коду на сайте B для выборки http://siteA/MyCode.js.
pimvdb
6
Но как??? Чтобы получить значение заголовка, нужно сначала извлечь ресурс, но ресурс является кросс-источником, и не должен ли браузер вообще блокировать запрос?
отметка
То, что вы описали, на самом деле напоминает другую практику, Политику безопасности контента
Алекс
3
@mark Вам не нужно извлекать ресурс, чтобы получить заголовки. Метод HTTP HEADER возвращает только заголовки. А в случае CORS проверка перед полетом выполняется с помощью метода HTTP OPTIONS, который также не возвращает тело. Ответ apsillers описывает это красиво stackoverflow.com/posts/10636765/revisions .
Мэтью

Ответы:

1446

Access-Control-Allow-Originявляется заголовок CORS (Cross-Origin Resource Sharing) .

Когда сайт A пытается извлечь контент с сайта B, сайт B может отправить Access-Control-Allow-Originзаголовок ответа, чтобы сообщить браузеру, что содержимое этой страницы доступно для определенных источников. (An происхождения является домен, плюс схема и номер порта ). По умолчанию, страницы сайта B являются не доступны для любого другого происхождения ; использование Access-Control-Allow-Originзаголовка открывает дверь для перекрестного доступа по конкретным запрашивающим источникам.

Для каждого ресурса / страницы, которые Сайт B хочет сделать доступными для Сайта A, Сайт B должен обслуживать свои страницы с заголовком ответа:

Access-Control-Allow-Origin: http://siteA.com

Современные браузеры не будут блокировать междоменные запросы напрямую. Если сайт A запрашивает страницу с сайта B, браузер фактически извлекает запрошенную страницу на сетевом уровне и проверяет, содержит ли заголовки ответа сайт A в качестве разрешенного домена запрашивающей стороны. Если объект B не указано , что сайт A имеет право доступа к этой странице, браузер запустит XMLHttpRequest«s errorсобытие и отрицают данные ответа на запрашивающий кода JavaScript.

Не простые запросы

То, что происходит на уровне сети, может быть немного сложнее, чем объяснено выше. Если запрос является «непростым» запросом , браузер сначала отправляет запрос «предварительных полетов» OPTIONS без данных, чтобы убедиться, что сервер примет запрос. Запрос является непростым, когда либо (или оба):

  • используя HTTP-глагол, отличный от GET или POST (например, PUT, DELETE)
  • использование непростых заголовков запросов; заголовки простых запросов:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(это только просто , когда его значение application/x-www-form-urlencoded, multipart/form-dataили text/plain)

Если сервер отвечает на предварительный просмотр OPTIONS с соответствующими заголовками ответа ( Access-Control-Allow-Headersдля непростых заголовков, Access-Control-Allow-Methodsдля непростых глаголов), которые соответствуют непростым глаголам и / или непростым заголовкам, то браузер отправляет фактический запрос.

Предположим, что Сайт A хочет отправить запрос PUT для /somePageнепростого Content-Typeзначения application/json, браузер сначала отправит предварительный запрос:

OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type

Обратите внимание, что Access-Control-Request-Methodи Access-Control-Request-Headersдобавляются браузером автоматически; Вам не нужно добавлять их. Этот предварительный просмотр OPTIONS получает заголовки успешного ответа:

Access-Control-Allow-Origin: http://siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type

При отправке фактического запроса (после выполнения предварительной проверки) поведение идентично тому, как обрабатывается простой запрос. Другими словами, непростой запрос, предварительная проверка которого прошла успешно, обрабатывается так же, как и простой запрос (т. Е. Сервер все еще должен отправить Access-Control-Allow-Originснова для фактического ответа).

Браузеры отправляют актуальный запрос:

PUT /somePage HTTP/1.1
Origin: http://siteA.com
Content-Type: application/json

{ "myRequestContent": "JSON is so great" }

И сервер отправляет обратно Access-Control-Allow-Origin, как это было бы для простого запроса:

Access-Control-Allow-Origin: http://siteA.com

Посмотрите Понимание XMLHttpRequest через CORS для получения дополнительной информации о непростых запросах.

apsillers
источник
4
Но MyCode.js не может достичь сайта B в первую очередь! Как этот заголовок попадет на клиента? Кстати, слава для легкой жизни планера в аватаре.
отметка
8
Я отредактировал с уточнением: браузер фактически выполняет сетевую выборку на сайте B для проверки Access-Control-Allow-Originзаголовка, но может не предоставить ответ на код JS на сайте A, если заголовок не позволяет сайту A иметь его. (PS Спасибо :))
Апсиллер
2
На самом деле, я не вижу никакой записи о загрузке в Fiddler, если только запрос перекрестного происхождения не будет одобрен. Интересно ...
отметка
23
@ Jwan622 Фундаментальный вопрос « почему? », Подобный этому, вероятно, выходит за рамки данного конкретного ответа, который касается правил и механики. По сути, браузер позволяет вам , человеку, сидящему за компьютером, видеть любой ресурс любого происхождения. Он запрещает скриптам (которые могут быть написаны кем-либо) читать ресурсы из источников, которые отличаются от источника страницы, на которой выполняется скрипт. Некоторые связанные вопросы: programmers.stackexchange.com/q/216605 и « Какова модель угрозы для той же политики происхождения?
Апсиллеры
3
В случае использования аутентификации, Access-Control-Allow-Originне принимает *в некоторых браузерах (FF и Chrome AFAIK). Так что в этом случае вы должны указать значение из Originзаголовка. Надеюсь, что это кому-нибудь поможет.
Жолти
123

Распределение ресурсов между источниками - CORS(кросс-доменный запрос AJAX AKA) - это проблема, с которой может столкнуться большинство веб-разработчиков. Согласно Same-Origin-Policy, браузеры ограничивают клиентский JavaScript в изолированной программной среде безопасности, обычно JS не может напрямую взаимодействовать с удаленным сервером. из другого домена. В прошлом разработчики создавали множество хитрых способов достижения запроса к междоменным ресурсам, чаще всего с использованием следующих способов:

  1. Используйте Flash / Silverlight или серверную часть в качестве «прокси» для связи с удаленным.
  2. JSON With Padding ( JSONP ).
  3. Встраивает удаленный сервер в iframe и связывается через фрагмент или имя window.name, см. Здесь .

У этих хитрых способов есть более или менее некоторые проблемы, например, JSONP может привести к дыре в безопасности, если разработчики просто «оценят» ее, и № 3 выше, хотя это работает, оба домена должны заключать строгие контракты друг с другом, это ни гибко, ни элегантно ИМХО:)

W3C представила Cross-Origin Resource Sharing (CORS) в качестве стандартного решения, чтобы обеспечить безопасный, гибкий и рекомендуемый стандартный способ решения этой проблемы.

Механизм

На высоком уровне мы можем просто считать, что CORS - это контракт между клиентским вызовом AJAX из домена A и страницей, размещенной в домене B, типичный запрос / ответ Cross-Origin будет выглядеть так:

Заголовки AJAX-запроса DomainA

Host DomainB.com
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json
Accept-Language en-us;
Accept-Encoding gzip, deflate
Keep-Alive 115
Origin http://DomainA.com 

Заголовки ответа DomainB

Cache-Control private
Content-Type application/json; charset=utf-8
Access-Control-Allow-Origin DomainA.com
Content-Length 87
Proxy-Connection Keep-Alive
Connection Keep-Alive

Синие части, которые я отмечал выше, были основными фактами, заголовок запроса «Происхождение» указывает, откуда исходит перекрестный запрос или запрос предварительной проверки », заголовок ответа« Access-Control-Allow-Origin »указывает, что эта страница допускает удаленный запрос от DomainA (если значение * указывает, разрешает удаленные запросы из любого домена).

Как я упоминал выше, W3 рекомендовал браузеру реализовать «предварительный запрос » перед отправкой фактически HTTP-запроса Cross-Origin, в двух словах это HTTP- OPTIONSзапрос:

OPTIONS DomainB.com/foo.aspx HTTP/1.1

Если foo.aspx поддерживает HTTP-глагол OPTIONS, он может вернуть ответ, как показано ниже:

HTTP/1.1 200 OK
Date: Wed, 01 Mar 2011 15:38:19 GMT
Access-Control-Allow-Origin: http://DomainA.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Max-Age: 1728000
Connection: Keep-Alive
Content-Type: application/json

Только если ответ содержит «Access-Control-Allow-Origin» И его значение равно «*» или содержит домен, который отправил запрос CORS, при выполнении этого обязательного условия браузер отправит фактический междоменный запрос и кеширует результат в " Preflight-Result-Cache ".

Я писал о CORS три года назад: HTTP-запрос AJAX Cross-Origin

Уэйн Е.
источник
Этот ответ заставил меня понять, почему я внезапно получил проблему, не используя этот заголовок для запросов POST и GET. Я случайно открыл файл index.html прямо с диска, поэтому URL-адрес, к которому клиент обращался на node.js, считался междоменным, в то время как он просто работал на localhost. Доступ через URL (как это обычно
делается
Может ли домен во внешней сети взаимодействовать с доменом во внутренней сети?
Si8
68

Вопрос слишком стар, чтобы ответить, но я публикую его для любой будущей ссылки на этот вопрос.

Согласно этой статье Mozilla Developer Network,

Ресурс делает HTTP-запрос перекрестного происхождения, когда запрашивает ресурс из другого домена или порта, отличного от того, который обслуживает сам первый ресурс.

введите описание изображения здесь

HTML страница обслуживается http://domain-a.comделает <img>запрос SRC для http://domain-b.com/image.jpg.
Сегодня многие страницы в Интернете загружают ресурсы, такие как таблицы стилей CSS , изображения и скрипты, из разных доменов (поэтому это должно быть круто).

Политика одного происхождения

По соображениям безопасности браузеры ограничивают перекрестные HTTP- запросы, инициируемые из сценариев .
Например, XMLHttpRequestи Fetchследуйте той же политике происхождения .
Таким образом, веб-приложение использует XMLHttpRequestили Fetchможет только отправлять HTTP-запросы в свой домен .

Обмен ресурсами между источниками (CORS)

Чтобы улучшить веб-приложения, разработчики попросили поставщиков браузеров разрешить междоменные запросы.

Механизм общего доступа к ресурсам между источниками (CORS) предоставляет веб-серверам средства управления междоменным доступом , которые обеспечивают безопасную междоменную передачу данных.
Современные браузеры используют CORS в контейнере API - например, XMLHttpRequestили Fetch-, чтобы снизить риски HTTP-запросов между источниками.

Как работает CORS ( Access-Control-Allow-Originзаголовок)

Википедия :

Стандарт CORS описывает новые заголовки HTTP, которые позволяют браузерам и серверам запрашивать удаленные URL-адреса только при наличии разрешения.

Хотя некоторая проверка и авторизация может выполняться сервером, браузер , как правило, несет ответственность за поддержку этих заголовков и соблюдение ограничений, которые они налагают.

пример

  1. Браузер отправляет OPTIONSзапрос с Origin HTTPзаголовком.

    Значением этого заголовка является домен, который обслуживал родительскую страницу. Когда страница http://www.example.comпытается получить доступ к данным пользователя service.example.com, следующий заголовок запроса будет отправлен service.example.com:

    Происхождение: http://www.example.com

  2. Сервер service.example.comможет ответить:

    • Access-Control-Allow-Origin(ACAO) заголовка в своем ответе указывает на происхождение которых сайты разрешены.
      Например:

      Access-Control-Allow-Origin: http://www.example.com

    • Страница с ошибкой, если сервер не разрешает перекрестный запрос

    • Access-Control-Allow-Origin(ACAO) заголовок с групповым символом , что позволяет все домены:

      Access-Control-Allow-Origin: *

PMPR
источник
1
Как установить ни один не разрешено тузы что-то вродеAccess-Control-Allow-Origin:null
Субин Чалил
2
Когда я не хочу разрешать кому-либо доступ к моим ресурсам через CORS, какое значение я должен установить Access-Control-Allow-Origin? Я имею в виду отрицаниеAccess-Control-Allow-Origin: *
Субин Чалил
4
Просто ничего не устанавливайте для этой цели
Pmpr
24

Всякий раз, когда я начинаю думать о CORS, моя интуиция о том, какой сайт размещает заголовки, неверна, как вы описали в своем вопросе. Для меня это помогает думать о цели той же политики происхождения.

Цель той же политики происхождения - защитить вас от вредоносного JavaScript на siteA.com, получающего доступ к частной информации, которую вы выбрали для обмена только с siteB.com. Без такой же политики происхождения JavaScript, написанный авторами siteA.com, может заставить ваш браузер отправлять запросы на siteB.com, используя ваши файлы cookie для аутентификации для siteB.com. Таким образом, siteA.com может украсть секретную информацию, которой вы делитесь с siteB.com.

Иногда вам нужно работать с междоменными доменами, в которые входит CORS. CORS ослабляет ту же политику происхождения для domainB.com, используя Access-Control-Allow-Originзаголовок для перечисления других доменов (domainA.com), которым доверяют запуск JavaScript, который может взаимодействовать с domainA. ком.

Чтобы понять, какой домен должен обслуживать заголовки CORS, подумайте об этом. Вы посещаете malware.com, который содержит некоторый JavaScript, который пытается сделать кросс-доменный запрос к mybank.com. Решение о том, устанавливает ли он заголовки CORS, ослабляют ли ту же политику происхождения, позволяющую JavaScript с вредоносного сайта взаимодействовать с ним, должен решать mybank.com, а не malware.com. Если бы malicous.com мог установить собственные заголовки CORS, разрешающие собственный доступ JavaScript к mybank.com, это полностью аннулировало бы ту же политику происхождения.

Я думаю, что причиной моей плохой интуиции является точка зрения, которую я имею при разработке сайта. Это мой сайт со всем моим JavaScript, поэтому он не делает ничего вредоносного, и я должен указать, с какими другими сайтами может взаимодействовать мой JavaScript. Когда на самом деле я должен думать, какие другие сайты JavaScript пытается взаимодействовать с моим сайтом, и должен ли я использовать CORS, чтобы разрешить их?

Дом
источник
1
Учитывая пункт 2, у вас есть сайт A, сайт B в обратном направлении в пункте 3? Я мог бы неправильно понять, но предыдущий абзац, кажется, подразумевает его сайт A, на котором запущен JS?
виолончель
11

1. Клиент загружает код JavaScript MyCode.js из http: // siteA - источник.

Код, который выполняет загрузку - ваш тег html-скрипта или xhr из javascript или чего-либо еще - поступил, скажем, с http: // siteZ . И когда браузер запрашивает MyCode.js, он отправляет заголовок Origin: «Origin: http: // siteZ », потому что он может видеть, что вы запрашиваете siteA и siteZ! = SiteA. (Вы не можете остановиться или помешать этому.)

2. Заголовок ответа MyCode.js содержит Access-Control-Allow-Origin: http: // siteB , что, как я думал, означало, что MyCode.js было разрешено делать ссылки на сайт B.

нет. Это означает, что только siteB разрешено делать этот запрос. Таким образом, ваш запрос MyCode.js от siteZ вместо этого получает ошибку, и браузер обычно ничего не дает. Но если вы заставите свой сервер вернуть ACAO: siteZ, вы получите MyCode.js. Или, если он отправит '*', это сработает, это впустит всех. Или если сервер всегда отправляет строку из заголовка Origin: ... но ... для безопасности, если вы боитесь хакеров ваш сервер должен разрешать отправлять сообщения только в короткий список, которому разрешено делать эти запросы.

Затем MyCode.js приходит с сайта A. Когда он отправляет запросы на сайт B, все они относятся к разным источникам, браузер отправляет Origin: siteA, и siteB должен взять сайт A, распознать его в коротком списке разрешенных запросчиков и отправить обратно ACAO: siteA. Только тогда браузер позволит вашему сценарию получить результат этих запросов.

OsamaBinLogin
источник
10

Используя React и Axios , присоедините прокси-ссылку к URL и добавьте заголовок, как показано ниже

https://cors-anywhere.herokuapp.com/ + Your API URL

Просто добавив ссылку «Прокси», вы сможете снова получить сообщение об отсутствии доступа. Следовательно, лучше добавить заголовок, как показано ниже.

axios.get(`https://cors-anywhere.herokuapp.com/[YOUR_API_URL]`,{headers: {'Access-Control-Allow-Origin': '*'}})
      .then(response => console.log(response:data);
  }

ВНИМАНИЕ: Не использовать в производстве

Это просто быстрое решение, если вы боретесь с тем, почему не можете получить ответ, вы МОЖЕТЕ использовать это. Но опять же это не лучший ответ для производства.

Получил несколько отрицательных голосов, и это полностью имеет смысл, я должен был добавить предупреждение давным-давно.

Давал Джардош
источник
19
Пожалуйста, не делай этого. Использование прокси-ссылки похоже на передачу пользовательских куки-файлов посреднику. Должно быть незаконно ИМХО
anthonymonori
это было полезно для меня! За исключением случаев, а не использовать * (который имеет проблемы с безопасностью), я ограничил контроль доступа к точному адресу я использую , чтобы узнать , с ... в моем случае « reqres.in/api/register »
C-Note187
9

Если вы хотите просто протестировать междоменное приложение, в котором браузер блокирует ваш запрос, вы можете просто открыть свой браузер в небезопасном режиме и протестировать свое приложение, не изменяя код и не делая ваш код небезопасным. Из MAC OS вы можете сделать это из терминальной линии:

open -a Google\ Chrome --args --disable-web-security --user-data-dir
Маурицио Бриоски
источник
9

Если вы используете PHP, попробуйте добавить следующий код в начале файла php:

Если вы используете localhost, попробуйте это:

header("Access-Control-Allow-Origin: *");

Если вы используете внешние домены, такие как сервер, попробуйте это:

header("Access-Control-Allow-Origin: http://www.website.com");
Мелвин Герреро
источник
7

Я работаю с Express 4 и узлом 7.4 и Angular, у меня была такая же проблема, мне помочь это:
а) сторона сервера: в файле app.js я даю заголовки для всех ответов, как:

app.use(function(req, res, next) {  
      res.header('Access-Control-Allow-Origin', req.headers.origin);
      res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
      next();
 });  

это должно быть раньше всех роутеров .
Я видел много добавленных заголовков:

res.header("Access-Control-Allow-Headers","*");
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');

но мне это не нужно,
б) на стороне клиента: в send ajax вам нужно добавить: "withCredentials: true", например:

$http({
     method: 'POST',
     url: 'url, 
     withCredentials: true,
     data : {}
   }).then(function(response){
        // code  
   }, function (response) {
         // code 
   });

удачи.

Изик Ф
источник
res.header('Access-Control-Allow-Origin', req.headers.origin);так же, какres.header('Access-Control-Allow-Origin', '*');
Aelfinn
4

Для обмена между источниками установите заголовок: 'Access-Control-Allow-Origin':'*';

Php: header('Access-Control-Allow-Origin':'*');

Узел: app.use('Access-Control-Allow-Origin':'*');

Это позволит обмениваться контентом для другого домена.

suryadev
источник
4

В Python я пользуюсь Flask-CORSбиблиотекой с большим успехом. Это делает работу с CORS супер простой и безболезненной. Я добавил код из документации библиотеки ниже.

Установка:

$ pip install -U flask-cors

Простой пример, который позволяет CORS для всех доменов на всех маршрутах:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route("/")
def helloWorld():
  return "Hello, cross-origin-world!"

Для более конкретных примеров см. Документацию. Я использовал простой пример, описанный выше, чтобы обойти проблему CORS в создаваемом ионном приложении, которое должно получить доступ к отдельному серверу-колбе.

peachykeen
источник
4

Исходя из моего собственного опыта, трудно найти простое объяснение, почему CORS даже вызывает озабоченность.

Как только вы поймете, почему это так, заголовки и обсуждение станут намного понятнее. Я сделаю это в несколько строк.


Это все о печенье. Файлы cookie хранятся на клиенте по их домену.

Пример истории: на вашем компьютере есть файл cookie для yourbank.com. Может быть, ваша сессия там.

Ключевой момент: когда клиент отправляет запрос на сервер, он отправляет файлы cookie, хранящиеся в домене, в котором находится клиент.

Вы вошли в свой браузер на yourbank.com. Вы просите просмотреть все ваши аккаунты. yourbank.comполучает кучу куки и отправляет обратно свой ответ (ваши аккаунты).

Если другой клиент отправляет серверу запрос на перекрестный источник , эти файлы cookie отправляются вместе, как и раньше. Рух Ро.

Вы просматриваете к malicious.com. Malicious делает кучу запросов в разные банки, в том числе yourbank.com.

Поскольку cookie-файлы проверены должным образом, сервер авторизует ответ.

Эти куки собраны и отправлены вместе - и теперь malicious.comесть ответ от yourbank.

Хлоп.


Итак, теперь несколько вопросов и ответов становятся очевидными:

  • «Почему бы нам просто не заблокировать браузер?» Ага. CORS.
  • "Как мы можем обойти это?" Пусть сервер скажет запрос, что CORS в порядке.
Бен
источник
3

Просто вставьте следующий код в ваш файл web.config.

Заметил, что вы должны вставить следующий код под <system.webServer>тегом

    <httpProtocol>  
    <customHeaders>  
     <add name="Access-Control-Allow-Origin" value="*" />  
     <add name="Access-Control-Allow-Headers" value="Content-Type" />  
     <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />  
    </customHeaders>  
  </httpProtocol>  
Джуборай Саркер
источник
0

Заголовок ответа Access-Control-Allow-Origin указывает, может ли ответ использоваться совместно с запрашивающим кодом из данного источника.

Header type Response       header
Forbidden header name      no

Ответ, который говорит браузеру разрешить коду из любого источника обращаться к ресурсу, будет включать следующее:

Access-Control-Allow-Origin: *

Для получения дополнительной информации посетите здесь ....

Алиреза
источник
0

Nginx и Appache

В дополнение к ответу apsillers я хотел бы добавить график вики, который показывает, когда запрос прост или нет (и перед отправкой запроса OPTIONS отправляется или нет)

Введите описание изображения здесь

Для простого запроса (например, горячие ссылки на изображения ) вам не нужно изменять файлы конфигурации вашего сервера, но вы можете добавить заголовки в приложение (размещенное на сервере, например, в php), как упоминает Мелвин Герреро в своем ответе - но помните : если вы добавите полный Заголовки cors на вашем сервере (config), и в то же время вы разрешаете простые cors в приложении (например, php), это не будет работать вообще.

И вот конфигурации для двух популярных серверов

  • включить CORS на Nginx ( nginx.confфайл)

  • включить CORS на Appache ( .htaccessфайл)

Камил Келчевски
источник