В целях безопасности браузер запрещает JS-скрипту одного сайта обращаться на другой сайт без специального разрешения. Разрешение это реализуется с помощью технологии CORS (Cross-Origin Resource Sharing). Рассмотрим пример.
Когда возникает проблема
Создадим простое Spring Boot приложение с единственным Rest-контроллером:
@RestController public class HelloController { @GetMapping("/api/hello") public String hello(){ return "Hello"; } }
Зависимость spring-boot-starter-security пока не будем добавлять в проект. Достаточно spring-boot-starter-web.
В браузере запрос http://localhost:8080/api/hello выполняется успешно:
Теперь запустим на другом порту localhost:4200 Angular-проект (статика и js), он будет выполнять ajax-запросы к нашему контроллеру.
Не обязательно проект должен быть на Angular, можно взять любой другой js-фреймворк или просто JQuery. Главное, мы имитируем обращение с другого сайта, то есть он должен быть на отдельном порту, не на 8080. Политика браузера такова, что другой порт для него считается такой же опасностью, как другой сайт.
Мы запустили Spring Boot приложение на localhost:8080, а Angular-клиент на порту localhost:4200.
Откроем в браузере localhost:4200 — откроется страница Angular-клиента, которая содержит JavaScript-код, выполняющий запрос http://localhost:8080/api/hello. В консоли мы увидим ошибку, что-то вроде:
Цитирую текстом:
Access to XMLHttpRequest at 'http://localhost:8080/api/hello' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Страница, естественно, не отображается. У новичка, не знакомого с CORS-политикой браузера, возникает искушение отключить весь CORS в Spring Security. Но у нас он еще не включен. Потому что дело не в Spring Security, а в браузере. И CORS придется не отключить, а наоборот, включить. И настроить.
Еще раз вернемся к ошибке:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Тут говорится, что в http-ответе на запрос http://localhost:8080/api/hello отсутствует заголовок Access-Control-Allow-Origin.
Что должно быть в http-ответе
Итак, чтобы браузер не выдавал ошибку, надо чтобы Spring Boot приложение явно указывало в http-ответе в заголовке Access-Control-Allow-Origin домен(ы), с которого разрешены запросы. То есть, чтобы не отсекать нашего клиента, запущенного на http://localhost:4200, в ответе должен содержаться такой заголовок:
Access-Control-Allow-Origin: http://localhost:4200
Подразумевается, что Spring Boot приложение должно знать этот сайт http://localhost:4200 и давать ему разрешение на запросы с помощью вышеприведенного заголовка. Spring Boot приложение должно включать этот заголовок в ответ.
Если бы наш Angular-клиент находился на сайте myangularclient.com, то ему требовался бы соответственно такой заголовок:
Access-Control-Allow-Origin: myangularclient.com
А такой заголовок со звездочкой разрешит запросы всем сайтам:
Access-Control-Allow-Origin: *
Но последнее не безопасно.
Итак, займемся добавлением заголовка.
Настройка Spring Security
Для начала добавим Spring Security в проект:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
Как только это сделано, включится принудительная аутентификация всех запросов по умолчанию, и при вводе в браузере запроса мы будем перенаправлены на страницу логина:
Исправим это, разрешив все запросы для всех пользователей (наш фокус — CORS, а не аутентификация):
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/**").permitAll(); } }
Теперь в браузере запрос http://localhost:8080/api/hello выполняется — возвращается hello, а вот при выполнении запроса с клиента возникает та же ошибка. Исправим ее.
Настройка CORS
Для этого реализуем еще метод addCorsMappings() интерфейса WebMvcConfigurer:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/**").permitAll(); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:4200") .allowedMethods("*"); } }
В методе мы говорим, каким сайтам по каким url и какими методами можно делать запросы к нашему приложению, запущенному на 8080.
Мы сказали, что:
- только сайт http://localhost:4200 может делать запросы
- запрос можно делать абсолютно всеми методами (GET, POST, PUT и т.д.)
- обращаться к нашему приложению можно по любому внутреннему url — addMapping(«/**»)
Теперь проверим, как это работает с Angular-клиента.
Проверка
На картинке всплывающая подсказка говорит, что мы обращаемся по запросу
http://localhost:8080/api/hello
Запрос и ответ содержат такие заголовки (запрос смотрите внизу, а ответ вверху, важное выделено красным):
Запрос
Host: localhost:8080 Origin: http://localhost:4200
В запросе в заголовках Origin и Host содержится откуда и куда идет запрос. По ним приложение Spring Boot поймет, надо ли вообще включать в ответ заголовок Access-Control-Allow-Origin (если Origin и Host одинаковые, то не надо, проблемы в браузере в этом случае в принципе не возникнет).
Заголовок Origin браузер добавляет автоматически, JS-программист не может его подделать.
Ответ
Далее наше Spring Boot приложение понимает, сайту http://localhost:4200 доступ разрешен (это мы настроили в методе addCorsMappings), так что в http-ответ оно включает такой заголовок:
Access-Control-Allow-Origin: http://localhost:4200
Браузер видит по этому заголовку, что сайту http://localhost:4200 доступ разрешен и не зажёвывает ответ с выбросом ошибки в консоль, как это делал раньше, а передает его клиенту. Все ок.
Исходный код Spring Boot примера (без Angular-клиента) доступен на GitHub.
Содержание
- Enabling Cross Origin Requests for a RESTful Web Service
- What You Will Build
- What You Need
- How to complete this guide
- Starting with Spring Initializr
- Adding the httpclient Dependency
- Create a Resource Representation Class
- Create a Resource Controller
- Enabling CORS
- Controller Method CORS Configuration
- Global CORS configuration
- Creating the Application Class
- Build an executable JAR
- Test the service
- Summary
- “What the CORS” ft Spring Boot & Spring Security
Enabling Cross Origin Requests for a RESTful Web Service
This guide walks you through the process of creating a “Hello, World” RESTful web service with Spring that includes headers for Cross-Origin Resource Sharing (CORS) in the response. You can find more information about Spring CORS support in this blog post.
What You Will Build
You will build a service that accepts HTTP GET requests at http://localhost:8080/greeting and responds with a JSON representation of a greeting, as the following listing shows:
You can customize the greeting with an optional name parameter in the query string, as the following listing shows:
The name parameter value overrides the default value of World and is reflected in the response, as the following listing shows:
This service differs slightly from the one described in Building a RESTful Web Service, in that it uses Spring Framework CORS support to add the relevant CORS response headers.
What You Need
About 15 minutes
A favorite text editor or IDE
You can also import the code straight into your IDE:
How to complete this guide
Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.
To start from scratch, move on to Starting with Spring Initializr.
To skip the basics, do the following:
Download and unzip the source repository for this guide, or clone it using Git: git clone https://github.com/spring-guides/gs-rest-service-cors.git
cd into gs-rest-service-cors/initial
When you finish, you can check your results against the code in gs-rest-service-cors/complete .
Starting with Spring Initializr
You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.
To manually initialize the project:
Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.
Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.
Click Dependencies and select Spring Web.
Click Generate.
Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.
If your IDE has the Spring Initializr integration, you can complete this process from your IDE.
You can also fork the project from Github and open it in your IDE or other editor.
Adding the httpclient Dependency
The tests (in complete/src/test/java/com/example/restservicecors/GreetingIntegrationTests.java ) require the Apache httpclient library.
To add the Apache httpclient library to Maven, add the following dependency:
The following listing shows the finished pom.xml file:
To add the Apache httpclient library to Gradle, add the following dependency:
The following listing shows the finished build.gradle file:
Create a Resource Representation Class
Now that you have set up the project and build system, you can create your web service.
Begin the process by thinking about service interactions.
The service will handle GET requests to /greeting , optionally with a name parameter in the query string. The GET request should return a 200 OK response with JSON in the body to represent a greeting. It should resemble the following listing:
The id field is a unique identifier for the greeting, and content is the textual representation of the greeting.
To model the greeting representation, create a resource representation class. Provide a plain old Java object with fields, constructors, and accessors for the id and content data, as the following listing (from src/main/java/com/example/restservicecors/Greeting.java ) shows:
Spring uses the Jackson JSON library to automatically marshal instances of type Greeting into JSON.
Create a Resource Controller
In Spring’s approach to building RESTful web services, HTTP requests are handled by a controller. These components are easily identified by the @Controller annotation, and the GreetingController shown in the following listing (from src/main/java/com/example/restservicecors/GreetingController.java ) handles GET requests for /greeting by returning a new instance of the Greeting class:
This controller is concise and simple, but there is plenty going on under the hood. We break it down step by step.
The @RequestMapping annotation ensures that HTTP requests to /greeting are mapped to the greeting() method.
The preceding example uses the @GetMapping annotation, which acts as a shortcut for @RequestMapping(method = RequestMethod.GET) . We use GET in this case because it is convenient for testing. Spring will still reject a GET request where the origin doesn’t match the CORS configuration. The browser is not required to send a CORS preflight request, but we could use @PostMapping and accept some JSON in the body if we wanted to trigger a pre-flight check.
@RequestParam binds the value of the name query string parameter into the name parameter of the greeting() method. This query string parameter is not required . If it is absent in the request, the defaultValue of World is used.
The implementation of the method body creates and returns a new Greeting object, with the value of the id attribute based on the next value from the counter and the value of the content based on the query parameter or the default value. It also formats the given name by using the greeting template .
A key difference between a traditional MVC controller and the RESTful web service controller shown earlier is the way that the HTTP response body is created. Rather than relying on a view technology to perform server-side rendering of the greeting data to HTML, this RESTful web service controller populates and returns a Greeting object. The object data is written directly to the HTTP response as JSON.
To accomplish this, the [ @RestController ] annotation assumes that every method inherits the @ResponseBody semantics by default. Therefore, a returned object data is inserted directly into the response body.
Thanks to Spring’s HTTP message converter support, the Greeting object is naturally converted to JSON. Because Jackson is on the classpath, Spring’s MappingJackson2HttpMessageConverter is automatically chosen to convert the Greeting instance to JSON.
Enabling CORS
You can enable cross-origin resource sharing (CORS) from either in individual controllers or globally. The following topics describe how to do so:
Controller Method CORS Configuration
So that the RESTful web service will include CORS access control headers in its response, you have to add a @CrossOrigin annotation to the handler method, as the following listing (from src/main/java/com/example/restservicecors/GreetingController.java ) shows:
This @CrossOrigin annotation enables cross-origin resource sharing only for this specific method. By default, its allows all origins, all headers, and the HTTP methods specified in the @RequestMapping annotation. Also, a maxAge of 30 minutes is used. You can customize this behavior by specifying the value of one of the following annotation attributes:
In this example, we allow only http://localhost:8080 to send cross-origin requests.
You can also add the @CrossOrigin annotation at the controller class level as well, to enable CORS on all handler methods of this class.
Global CORS configuration
In addition (or as an alternative) to fine-grained annotation-based configuration, you can define some global CORS configuration as well. This is similar to using a Filter but can be declared within Spring MVC and combined with fine-grained @CrossOrigin configuration. By default, all origins and GET , HEAD , and POST methods are allowed.
The following listing (from src/main/java/com/example/restservicecors/GreetingController.java ) shows the greetingWithJavaconfig method in the GreetingController class:
The difference between the greetingWithJavaconfig method and the greeting method (used in the controller-level CORS configuration) is the route ( /greeting-javaconfig rather than /greeting ) and the presence of the @CrossOrigin origin.
The following listing (from src/main/java/com/example/restservicecors/RestServiceCorsApplication.java ) shows how to add CORS mapping in the application class:
You can easily change any properties (such as allowedOrigins in the example), as well as apply this CORS configuration to a specific path pattern.
You can combine global- and controller-level CORS configuration.
Creating the Application Class
The Spring Initializr creates a bare-bones application class for you. The following listing (from initial/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java ) shows that initial class:
You need to add a method to configure how to handle cross-origin resource sharing. The following listing (from complete/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java ) shows how to do so:
The following listing shows the completed application class:
@SpringBootApplication is a convenience annotation that adds all of the following:
@Configuration : Tags the class as a source of bean definitions for the application context.
@EnableAutoConfiguration : Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings. For example, if spring-webmvc is on the classpath, this annotation flags the application as a web application and activates key behaviors, such as setting up a DispatcherServlet .
@ComponentScan : Tells Spring to look for other components, configurations, and services in the com/example package, letting it find the controllers.
The main() method uses Spring Boot’s SpringApplication.run() method to launch an application. Did you notice that there was not a single line of XML? There is no web.xml file, either. This web application is 100% pure Java and you did not have to deal with configuring any plumbing or infrastructure.
Build an executable JAR
You can run the application from the command line with Gradle or Maven. You can also build a single executable JAR file that contains all the necessary dependencies, classes, and resources and run that. Building an executable jar makes it easy to ship, version, and deploy the service as an application throughout the development lifecycle, across different environments, and so forth.
If you use Gradle, you can run the application by using ./gradlew bootRun . Alternatively, you can build the JAR file by using ./gradlew build and then run the JAR file, as follows:
If you use Maven, you can run the application by using ./mvnw spring-boot:run . Alternatively, you can build the JAR file with ./mvnw clean package and then run the JAR file, as follows:
The steps described here create a runnable JAR. You can also build a classic WAR file.
Logging output is displayed. The service should be up and running within a few seconds.
Test the service
Now that the service is up, visit http://localhost:8080/greeting in your browser where you should see:
Provide a name query string parameter by visiting http://localhost:8080/greeting?name=User . The value of the content attribute changes from Hello, World! to Hello User! , as the following listing shows:
This change demonstrates that the @RequestParam arrangement in GreetingController works as expected. The name parameter has been given a default value of World but can always be explicitly overridden through the query string.
Also, the id attribute has changed from 1 to 2 . This proves that you are working against the same GreetingController instance across multiple requests and that its counter field is being incremented on each call, as expected.
Now you can test that the CORS headers are in place and allow a Javascript client from another origin to access the service. To do so, you need to create a Javascript client to consume the service. The following listing shows such a client:
First, create a simple Javascript file named hello.js (from complete/public/hello.js ) with the following content:
This script uses jQuery to consume the REST service at http://localhost:8080/greeting . It is loaded by index.html , as the following listing (from complete/public/index.html ) shows:
This is essentially the REST client created in Consuming a RESTful Web Service with jQuery, modified slightly to consume the service when it runs on localhost at port 8080. See that guide for more details on how this client was developed.
To start the client running on localhost at port 8080, run the following Maven command:
If you use Gradle, you can use this command:
Once the app starts, open http://localhost:8080 in your browser, where you should see the following:
To test the CORS behaviour, you need to start the client from another server or port. Doing so not only avoids a collision between the two applications but also ensures that the client code is served from a different origin than the service. To start the app running on localhost at port 9000 (as well as the one that is already running on port 8080), run the following Maven command:
If you use Gradle, you can use this command:
Once the app starts, open http://localhost:9000 in your browser, where you should see the following:
If the service response includes the CORS headers, then the ID and content are rendered into the page. But if the CORS headers are missing (or insufficient for the client), the browser fails the request and the values are not rendered into the DOM.
Summary
Congratulations! You have just developed a RESTful web service that includes Cross-Origin Resource Sharing with Spring.
Источник
“What the CORS” ft Spring Boot & Spring Security
Ok. One fine day, I was trying to build a simple Rest API that exposed endpoints to create,read,update,delete a single resource on my server. I wanted to use React to make a small form that will submit these requests.
Beware, the following React form is so advanced. You have to be atleast a Ninja level React developer to come up with this kind of form.
Dont be jealous guys.!
So my Rest Api (made with spring boot) was running on ‘ http://localhost:8080’ & my React App was running on ‘ http://localhost:3000’
Code was all ready and prepped to make that first ever post request to create the resource on server. I was waiting eagerly as the folks at JPL during perseverance touch down.
Then I saw this on my console
And when I went to my network tab in ‘developer console’ to dig further. I saw these two requests lying around
I was using the fetch api to make the below POST request from my react code.
After hitting that submit button from my page, i was confused of two things:
- Why did my POST fail
- Why are there two requests in network tab of my server as shown in the developer console screenshot
CORS is a way for browsers to understand if a particular javascript code running on it(Ex: My React Code — http://localhost:3000)is allowed to request a resource from the server(My Spring Boot Rest Api running on the server — http://localhost:8000) . If they are not allowed by servers, then browsers simply throw a CORS exception.
Lets try to understand this with error message i received in the console:
A ccess to fetch at ‘ http://localhost:8080/airports’ from origin ‘ http://localhost:3000′ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
Here the key word to understand is ‘origin’. ‘origin’ is the combination of ‘protocol://host:port’. So the request is being made from an origin called ‘http://localhost:3000’ and the request is sent to origin ‘http://localhost:8080’. So these are two different origins trying to communicate. So in these cases , the browsers verify if the requestor (http://localhost:3000) is allowed to request a resource from requestee (http://localhost:8080). If requestor is not allowed to , then we see this nasty CORS error while making requests.
But why do we see two requests happening in network tab?
If you notice carefully , one request is having the Type = preflight and another one (i.e., the actual fetch making POST) request has type = ‘CORS error’ from screenshot above. So Browsers don’t immediately send the POST request first, they try to check which “origin” is whitelisted/allowed by server first. In order to do this check, they send out an OPTIONS request to the server. This is often referred to “preflight” request.
So what is the browser actually expecting from this preflight request?
Browser is expecting to receive a response header back from the server which tells it that “am cool with having this request”.
And which response header is that exactly?
“ access-control-allow-origin” — This is the header where the whitelisted origins are mentioned. If localhost:3000 is present on the whitelisted header then we are open for business.
Lets see what is in the failed preflight request.
The Request Method is ‘OPTIONS’ and its forbidden (403) to make that request on the server. And my Response headers don’t have anything mentioned as ‘Access-Control-Allow-Origin’. So Basically, The preflight request itself failed because “OPTIONS” request method was not defined on the server side.
All I have at my controller are these basic CRUD request mappings (GET,POST,PUT,DELETE)
So now the original question? How do i allow preflight to check which origin is allowed and how do i whitelist “http://localhost:3000” as a qualified origin.
Spring Boot offers a simple solution through its annotation @CrossOrigin. Simply put it on top of your controller class like this
Now I restarted my spring boot application and retried posting the resource.
You will see now that both preflight and the post request made it out with ‘200 — A Ok ;)’
Lets examine the preflight request and response headers now.
In the request headers of preflight, the request method is ‘OPTIONS’ and in response headers, you will notice that
‘Access-Control-Allow-Origin’ : * (This indicates that any origin can make a request to resource on the server, so my react application also qualifies to send the request)
‘Access-Control-Allow-Methods’ : POST (preflight confirmed that POST that is about to be made is qualified)
Before making the @CrossOrigins change on the server, preflight request got 403 status since OPTIONS was not allowed on the server. But now you can see that all the request methods are being allowed by the server. So preflight which is “OPTIONS” request goes through fine even if it doesn’t have any endpoint declared on the server.
Just declaring @CrossOrigins on the controller opens the gates for all the origins and methods to come and hit your server. But in a serious application, we wouldn’t want that. So you can customize the origins and methods that you want to filter at server by using these attributes in @CrossOrigins
(This will only allow my react app to request)
2. methods=,RequestMethod. POST,RequestMethod. PUT,RequestMethod. DELETE>
(You can define what methods are allowed from requests)
There are others to declare what headers are allows, and if credentials are allowed or not etc. You can explore them on your own
Phase II — Attach Basic Authentication to my requests using spring security
Ok. After this confidence boost, i wanted to try attaching spring security to my rest api. Wanted to allow basic authentication as a way to authenticate the requests
Being a Ninja that i am , Went and added Spring security starter dependencies to my pom.xml and udpated maven dependencies and started up my server. Once server restarts, spring security puts in a layer of authentication to the application with default username as ‘user’ and password is some hash that spring security generates randomly everytime.
Now, I made the same post request from react application.
And once again, the dreaded CORS exception was back
Went into network to debug further.
There was only one post request and no preflight request.The response to the post request doesn’t have the ‘Access-Control-Allow-Origin’ set. Also we see new WWW-Authenticate:Basic realm=”Realm” header.
So basically after enabling the spring security, we are expected to do two things:
- Send the basic authentication request from our client while making request
- Make our server send the Access-Control-Allow-Origin Flag indicating that the react application is qualified origin.
But wait, we already mentioned @CrossOrigins at our controller didn’t we?
Yeah, But they aren’t being considered when spring security is enabled, we need a way through this.
Lets, attack this one step at a time.
- Send the basic authentication request from our client while making request
Since am using fetch, in the parameters i send while making the POST call from React, I need to attach the below header information:
lets say my user name is — user
password is — harambe
I can use a function in react called btoa and pass username and password to it like — btoa(user:harambe). The btoa method returns the base64 encoded string. So this looks something like this
Don’t forget to keep between Basic and btoa(username:password) method. This helps with authentication.
Now lets try that post call again
Well, well , well — who we have here. Went to network to see both preflight and post request
The pattern feels the same like what we have encountered before adding spring security to the project. One preflight(OPTIONS) request that is unauthorized and another CORS exception in fetch POST call(the actual call).
Digging into the preflight request and response , i see that no allow-access-control-origin returned by server and it was expecting authentication even on the OPTIONS request
After spending a good half of my weekend into looking for a way to configure spring security to allow cross origin request, i stumbled upon this piece of code
Configure the spring security config by calling these methods that come out of the box in your main Spring Application that hosts this Rest API.
here the method
- cors() -> tells spring security to enable cors. So Now server is ready to whitelist/filter the origins. So be mindful that we are still going to set the origins list
- httpBasic() -> tells spring security that clients are going to authenticate the requests through basic authentication method. So spring security is tuned to read the ‘Authorization’:’Basic #hash’ that we are sending from client. If its a valid authentication details, spring security will let us through
For the rest of the methods you see on the code, i will plan another blog post.
Now, cors() is just enabled, but we still have to configure which origins are whitelisted.
So, here you see in the controller, I have added my
- origins = “http://localhost:3000” .which is my react code
- allowCredentials = true. This is important if you are using any form of authentication
By default, the @CrossOrigin allows all the request methods and headers. So
preflight request wouldn’t be having a problem now.
Now, let us hit that post button again
Finally, The sweetness of Glory.
Please ignore the second request here , its not from preflight. I wrote a logic to do ‘Get’ after posting something to list the items as u see in screenshot.
Lets check the POST request and response headers affected with our configuration.
You can see that ‘Access-Control-Allow-Credentials’ flag is set to true & also allow-origin shows the react application origin.
Источник
In this post, let’s find out what is the CORS policy, how to implement in the Spring Boot and Spring Security, finally how to resolve most common error Access-Control-Allow-Origin => Missing
Topics are:
- Github Link
- What is the CORS?
- What do we mean by different origin ?
- Why do we need a Cors policy ?
- How CORS works ?
- What do we mean by Simple Requests ?
- Project Setup
- Getting Response From the Same Origin
- Error: Access-Control-Allow-Origin => Missing
- CORS doesn’t do anything about the call of the endpoint
- How to resolve Access-Control-Allow-Origin => Missing Error
- CORS support through Spring Security
- CORS Setup through MVC Application
Github Link
If you only need to see the code, here is the github link
What is the CORS?
Cross-Origin Resource Sharing (CORS) is a protocol that enables scripts running on a browser client to interact with resources from a different origin.
We need this policy, because
XMLHttpRequest
andfetch
follows the same-origin-policy and that leads JavaScript can only make calls to URLs that lives on the same origin where the script is running.
First let’s define what do we mean by different origin?
What do we mean by different origin?
Two origins are different if they have:
- different schemes (
HTTP
ORHTTPS
) - different domains (
sample.com
VSapi.sample.com
VSanother.com
) - different ports (
sample.com:8081
VSsample.com:8080
)
Why do we need a Cors policy ?
In generally speaking, your web application should never interact with resources from a different origin. However today web application structure, you will mostly have an backend and frontend which are running on different ports. Therefore you somehow guarantee the communication between two sides.
Web browsers can use headers related to CORS to determine whether or not an
fetch
call should continue or fail
There are a few headers, but most important one is the Access-Control-Allow-Origin
which tells browsers to allow that origin to access the resource
- Example:
Access-Control-Allow-Origin: *
=> if your back-end application runs on the domain calledapi.sample.com
than this header says that every other origin can access theapi.sample.com
resources. For instances, web browsers that run scripts on the domainssample.com
oranother.com
can access(make request) to your domain:api.sample.com
Access-Control-Allow-Origin: http://sample.com
=> only the web browsers that run scripts on the domainhttp://sample.com
can make request to your domain.
Be carefully as a developer you are not responsible to make request, web browser will decide it.
How CORS works ?
- Cors works by adding new Http Headers that let servers describe which origins are permitted to read that information from a web browser.
Be careful, it says … from a web browser . That’s means you can send a curl request to the server ? Right !!
-
CORS specification says that any mutating request (requests that change something in the server such as
POST,PUT,DELETE
) must be done in the following way:- First, browser must do a preflight request and ask the supported methods to the server with the HTTP OPTIONS. (Requests that do not need any preflight request are called simple requests)
- After getting the approval from the server, then web browser can send the actual request.
Server can also inform the clients whether the credentials should be send with requests.
What do we mean by Simple Requests ?
For more detail explanation, please refer to the https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
A “simple request” is one that meets all the following conditions:
-
One of the allowed methods:
- GET
- HEAD
- POST (with limited content-types)
-
Except for the headers automatically set by the user agent (such as Connection, User-Agent), the only headers, which are allowed to be manually set, are considered as simple request:
- Accept
- Accept-Language
- Content-Language
- Content-Type (but only allowed ones)
In other words, even if you match with allowed methods, content-type, and other conditions, if there is an header except the ones above (and also except the ones that are automatically set by the user agent), then it will be not be considered as simple request
-
The allowed values for Content-Type
application/x-www-form-urlencoded
multipart/form-data
text/plain
Basically that’s means if you send POST request with content-type
application/json
, web browsers will think that your request is not a simple request
-
No
ReadableStream
object is used in the request -
If you send request with
XMLHttpRequest
and if you do any of the following one, then your request will not be considered as simple request:- If you register an event listener for the object returned by
XMLHttpRequest.upload
- If you register an event listener for the object returned by
You will understand better when we are working on spring boot application.
Project Setup
To simulate CORS, first create a simple spring boot project. This project will contain the following dependencies:
- Spring Web
- Spring Security
- Thymeleaf
Create home page controller and corresponding html pages inside resources/templates
@Controller
public class HomePageController {
@GetMapping
public String homePage() {
return "homePage";
}
@PostMapping("/post")
@ResponseBody
public String ajaxRequest() {
return "AJAX_RESPONSE_FROM_SPRING";
}
}
homePage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>HomePage</title>
</head>
<body>
<h1>Home Page</h1>
<p id="ajaxResponse"></p>
<script>
async function getData(url) {
var response = await fetch(url, { method: "POST" });
var result = await response.text();
var ajaxResponse = document.getElementById("ajaxResponse");
ajaxResponse.innerHTML = result;
}
const url = "http://localhost:8080/post";
getData(url);
</script>
</body>
</html>
- For this sample project, I will disable the CSRF protection (don’t do this in production)
- And also I will allow all request to be accessed without login.
@Component
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().permitAll();
}
}
Getting Response From the Same Origin
After run the project, you will see the AJAX_RESPONSE_FROM_SPRING in the page. Because you are in the same origin as the web application
Error: Access-Control-Allow-Origin => Missing
Instead of opening the http://localhost:8080/
, open the http://127.0.0.1:8080/
. In the web console you will encounter the error like this one:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/post. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
You will see this response, because you are sending request in different origin and fetch API
blocks the response
Open the network tab when you are sending request from 127.0.0.1, and find your post request. Here is the my request’s header:
POST /post HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: */*
Accept-Language: en-US,tr;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8080/
Origin: http://127.0.0.1:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 0
Please look at the origin field. This field says that request was generated from the origin http://127.0.0.1:8080
Now look at the response’s headers:
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 25
Date: Thu, 13 May 2021 10:37:44 GMT
Keep-Alive: timeout=60
Connection: keep-alive
As you can see, Spring did not return any header(s) related to the CORS (such Access-Control-Allow-Origin
), which Spring basically tells the browsers:
- Hey browsers when a script makes a request, then show the response, if and only if that request was made from the origin
http://localhost:8080
, otherwise blocks the response.
Be careful it blocks the response, that doesn’t mean browser also blocks the request(s).
In other words, cors doesn’t do anything about the call of the endpoint
CORS doesn’t do anything about the call of the endpoint
Update the post method and send the ajax request from the origin http:127.0.0.1
:
package com.mehmetozanguven.springsecuritycorssetup.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomePageController {
// ..
@PostMapping("/post")
@ResponseBody
public String ajaxRequest() {
System.out.println("Method called");
return "AJAX_RESPONSE_FROM_SPRING";
}
}
Even browser says ..Cross-Origin Request Blocked: The Same Origin Policy.. , look at the console of the spring application:
2021-05-13 13:57:04.718 INFO 67306 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-05-13 13:57:04.725 INFO 67306 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 7 ms
Method called
That’s means endpoint was actually called. Therefore CORS:
Does not do anything related to call of the endpoint. It just blocks the response to be accessed
Does not protect the mutation operation. In our case POST endpoint (mutation operation) was actually called, therefore something in the web application will be updated (database, cache etc ..)
How to resolve Access-Control-Allow-Origin => Missing Error
There are many ways to resolve this error. I will just show two of them.
NOTE: Please use one of the method, do not implement the both.
CORS support through Spring Security
@Component
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(List.of("*"));
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setMaxAge(Duration.ofMinutes(10));
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().permitAll();
http.cors(); // add this line;
}
}
CORS Setup through MVC Application
Create a WebMvcConfigurer
bean
@Component
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().permitAll();
}
}
…
CORS explained in detail
CORS is a W3C standard, the full name is Cross-origin resource sharing.
It allows the browser to cross-origin server, issued XMLHttpRequest/fetch
request, thus overcoming the AJAX can only be used in the same source of the limitations.
1. Introduction
CORS requires both browser and server support. Currently, all browsers support this feature, and Internet Explorer cannot be lower than IE10.
The entire CORS communication process is done automatically by the browser, without user involvement. For developers, CORS communication with the same source of AJAX communication no difference, the code is exactly the same. Once the browser finds the AJAX request cross-source, it will automatically add some additional headers, and sometimes one more additional request, but the user will not feel it.
Therefore, the key to achieve CORS communication is the server. As long as the server implements the CORS specification, it can communicate across sources.
2. Two types of requests
Browsers divide CORS requests into two categories: simple requests and not-so-simple requests.
As long as the following two conditions are met, it is a simple request.
-
The request method is one of the following three methods.
- HEAD
- GET
- POST
-
The HTTP headers do not exceed the following fields.
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type: limited to three values
application/x-www-form-urlencoded
,multipart/form-data
,text/plain
This is to be compatible with forms, because historically forms have been able to send cross-domain requests. AJAX’s cross-domain design is that, as long as the form can send, AJAX can send directly.
Where the above two conditions are not met at the same time, it is not a simple request.
The browser processing of these two requests, is not the same.
3. Simple request
3.1 Basic flow
For a simple request, the browser makes a CORS request directly. Specifically, it adds an Origin
field to the header.
Here is an example, the browser finds that the cross-origin AJAX request is a simple request, it automatically adds an Origin
field to the header information.
|
|
In the above header, the Origin
field is used to indicate the source (protocol + domain + port) from which this request is coming. Based on this value, the server decides whether to grant the request or not.
If the source specified by Origin
is not within the scope of the permission, the server returns a normal HTTP response. The browser finds that this response does not contain the Access-Control-Allow-Origin
field in its headers (see below for details) and knows that there is an error, thus throwing an error that is caught by the onerror
callback function of XMLHttpRequest
. Note that such errors cannot be identified by status codes, as the status code of an HTTP response can be 200.
If the domain name specified by Origin
is within the permitted range, the server returns a response with a few extra header fields.
|
|
The above header contains three fields related to CORS requests, all starting with Access-Control-
.
-
Access-Control-Allow-Origin
This field is required. Its value is either the value of the
Origin
field at the time of the request, or a*
that indicates that a request for an arbitrary domain name is accepted. -
Access-Control-Allow-Credentials
This field is optional. Its value is a boolean indicating whether cookies are allowed to be sent, and by default, cookies are not included in CORS requests. A value of
true
means that the server has explicitly given permission for the cookie to be included in the request and sent to the server together. This value can also only be set totrue
, so if the server does not want the browser to send cookies, just delete the field. -
Access-Control-Expose-Headers
This field is optional. CORS request, the
XMLHttpRequest
objectgetResponseHeader()
method can only get six basic fields:Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
. If you want to get other fields, you must specify them in theAccess-Control-Expose-Headers
. In the above example,getResponseHeader('FooBar')
will successfully get the value of theFooBar
field.The wildcard character:
*
, proposed in the latest specification, indicates that the client is allowed to access all header. is not yet implemented in some browsers.
3.2 withCredentials property
As mentioned above, CORS requests do not send cookies and HTTP authentication information by default. If a cookie is to be sent to the server, on the one hand, the server has to agree to respond to the Access-Control-Allow-Credentials
field.
|
|
On the other hand, the developer must turn on the withCredentials
property in the AJAX request
|
|
Otherwise, even if the server agrees to send a cookie, the browser will not send it. Or, if the server asks to set a cookie, the browser will not handle it.
However, some browsers will still send cookies together if the withCredentials
setting is omitted, in which case withCredentials
can be turned off explicitly.
|
|
Note that to send a cookie, Access-Control-Allow-Origin
cannot be set to an asterisk, but must specify a domain name that is explicit and consistent with the requested page. At the same time, cookies still follow the same origin policy, only cookies set with the server domain will be uploaded, cookies from other domains will not be uploaded, and the document.cookie
in the (cross-origin) original web code will not be able to read cookies from the server domain.
4. Non-simple requests
4.1 Preflight Requests
A non-simple request is one that has special requirements for the server, such as a request method of PUT
or DELETE
, or a Content-Type
field of type application/json
.
A CORS request that is not a simple request adds an HTTP query request, called a preflight, before the formal communication.
The browser first asks the server if the domain name of the current web page is in the server’s permission list, and which HTTP verbs and header fields can be used. Only when it gets a positive answer does the browser issue a formal XMLHttpRequest
request, otherwise it throws an exception.
Here is a JavaScript script for the browser.
|
|
In the above code, the HTTP request method is PUT
and sends a custom header X-Custom-Header
.
The browser finds that this is a non-simple request and automatically issues a “preflight” request to ask the server to confirm that it can do so. Here is the HTTP header for this “preflight” request.
|
|
The request method used for the “preflight” request is OPTIONS
, which indicates that the request is for interrogation. Inside the header, the key field is Origin
, which indicates the source of the request.
In addition to the Origin
field, the header of a “preflight” request includes two special fields.
-
Access-Control-Request-Method
This field is required to list which HTTP methods will be used by the browser’s CORS request, in the above example
PUT
. -
Access-Control-Request-Headers
This field is a comma-separated string that specifies the additional header field that will be sent by the browser CORS request, in the above example
X-Custom-Header
.
4.2 Response to Preflight Requests
After the server receives the Preflight request, it checks the Origin
, Access-Control-Request-Method
and Access-Control-Request-Headers
fields and confirms that cross-origin requests are allowed, then it can respond.
|
|
The key thing in the HTTP response above is the Access-Control-Allow-Origin
field, which indicates that http://api.bob.com
is allowed to initiate the request. This field can also be set to an asterisk to indicate consent to any cross-origin request.
|
|
If the server denies the preflight request, a normal HTTP response is returned, but without any CORS-related header fields. At this point, the browser determines that the server did not agree to the preflight request and therefore triggers an error that is caught by the onerror
callback function of the XMLHttpRequest
object. The console will print the following error message.
|
|
The other CORS-related fields that the server responds to are listed below.
|
|
-
Access-Control-Allow-Methods
This field is required and its value is a comma-separated string indicating all methods supported by the server for cross-domain requests. Note that all supported methods are returned, not just the one requested by the browser. This is to avoid multiple “preflight” requests.
-
Access-Control-Allow-Headers
The
Access-Control-Allow-Headers
field is required if the browser request includes theAccess-Control-Request-Headers
field. It is also a comma-separated string indicating all header fields supported by the server, not limited to the fields requested by the browser in the Preflight. -
Access-Control-Allow-Credentials
This field has the same meaning as in the case of a simple request.
-
Access-Control-Max-Age
This field is optional and is used to specify the validity of this preflight request in seconds. In the above result, the validity period is 20 days (1728000 seconds), which means that the response is allowed to be cached for 1728000 seconds (i.e. 20 days), during which time another preflight request does not have to be issued.
4.3 Normal browser request and response
Once the server has passed the “preflight” request, each subsequent normal browser CORS request will have an Origin
header field, just like a simple request. The server’s response will also have an Access-Control-Allow-Origin
header field.
Here is the normal CORS request from the browser after the “preflight” request.
|
|
The Origin
field in the above header is automatically added by the browser.
Here is the normal response from the server.
|
|
In the above header, the Access-Control-Allow-Origin
field is always included in every response.
CORS is used for the same purpose as JSONP, but is more powerful than JSONP. JSONP only supports
GET
requests, while CORS supports all types of HTTP requests. the advantages of JSONP are support for older browsers and the ability to request data from sites that do not support CORS.
Spring With CORS
spring
provides a number of ways to implement CORS
.
@CrossOrigin
CORS can be easily implemented using the @CrossOrigin
annotation. The code is as follows.
|
|
Note that When allowedCredentials
is true
, allowedOrigins
cannot contain the special value *
because it cannot be set in the Access-Control-Allow-Origin
response header. To allow credentials from a set of sources, list them explicitly or consider using allowedOriginPatterns
instead.
Otherwise, an exception will be thrown when the application starts.
|
|
WebMvcConfiguration
Global CORS configuration can be done through the addCorsMappings
method of the WebMvcConfiguration
configuration interface.
|
|
Similarly, if allowCredentials
is true
, allowedOrigins
cannot be *
.
CorsFilter
CorsFilter
is specifically designed to handle CORS requests. This approach is the most flexible and allows you to write code to handle CORS requests.
|
|
Reference
- https://www.ruanyifeng.com/blog/2016/04/cors.html
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- https://spring.io/guides/gs/rest-service-cors/
- https://spring.io/blog/2015/06/08/cors-support-in-spring-framework