Skip to content

CORS - Cross Origin Resource Sharing#

What Is The CORS?#

  • CORS - Cross Origin Resource Sharing is a protocol that enables scripts running on a browser client to interact with resources from a different origin.
  • For example, if an UI app wishes to make an API call running on a different domain, it would be blocked from doing so by default due to the CORS. So CORS is not a security issue/attack but the default protection provided by Web Browser to stop sharing the data/communication between different origins.
  • Other origins mean the URL being accessed differs from the location that the JavaScript is running from by having:
    • A different schema (HTTP or HTTPS)
    • A different domain
    • A different port

 #zoom

  • However, when a server has been configured correctly to allow cross-origin resource sharing, some special headers will be included. Their presence can be used to determine that a request supports CORS. Web browsers can use these headers to determine whether a request should continue or fail.
  • First the browser sends a pre-flight request to the backend server to determine whether it supports CORS or not. The server can then respond to the pre-flight request with a collection of headers.
Header Discription Example
Access-Control-Allow-Origin Defines which origins may have access to the resource. "*" represent any origin Access-Control-Allow-Origin: *
Access-Control-Allow-Methods Indicates the allowed HTTP methods for cross-origin requests Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers response header is used in response to a preflight request which includes the Access-Control-Request-Headers to indicate which HTTP headers can be used during the actual request. "*" represent any origin Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials Indicates whether or not the response to the request can be exposed when the credentials flag is true. Access-Control-Allow-Credentials: true
Access-Control-Max-Age Defines the expiration time (second) of the result of the cached preflight request Access-Control-Max-Age: 600 (10 minutes)

 #zoom

CORS Configuration In Spring Security#

Configure Global CORS#

  • Base on the current implementation of spring security example that implemented in Custom Authentication Provider. So now, we will continue to add CORS configuration for our spring security application.
  • Full source code here
  • So let's add the corsConfigurationSource bean in the class ProjectSecurityConfig as below.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.spring.security.spring.security.custom.cors.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and().authorizeRequests()
                .antMatchers("/v1/user").authenticated()
                .antMatchers("/v1/accounts/**").authenticated()
                .antMatchers("/v1/balance").authenticated()
                .antMatchers("/v1/loan").authenticated()
                .antMatchers("/v1/card").authenticated()
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • So firstly we will create the CorsConfiguration and set some parameters as the table below.
Method Corresponding Header Example Value
setAllowedHeaders() Access-Control-Allow-Headers *
setAllowedOriginPatterns() Access-Control-Allow-Origin *
setAllowedMethods() Access-Control-Allow-Methods *
setAllowCredentials() Access-Control-Allow-Credentials true
  • Then we will create corsConfigurationSource bean by creating UrlBasedCorsConfigurationSource and set the CorsConfiguration with the path pattern that we want to apply. In this example we will apply this CorsConfiguration for all path, so we will use registerCorsConfiguration() method with /**.
  • If you just define the bean for CORS configuration, so it will not work, you have to edit the method in the configure() by adding the method cors()... to apply your new CORS configuration in Spring Security.

Testing#

  • Now, let's create a simple html file which contain a javascript to call api of our spring security application and this html will be served by another http web server.
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <script>
        get();
        async function get() {
            try {
                const res = await fetch('http://localhost:8080/v1/contact',
                    {
                        method: 'get',
                        headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json'
                        }
                    });
                const json = await res.json();
                console.log(json);
            } catch (err) {
                console.info('err', err);
            }
        } 
    </script>

</body>

</html>
  • Then if you are using python on your machine, then use the command below to start a simple http web server. By default it will run at port 8000.
1
python3 -m http.server
  • Now, we will try starting our spring security application without CORS configuration first, so we will remove the CORS configuration as below. Then start the application, it will run at port 8080.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//...........

    protected void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests()
                                .antMatchers("/v1/user").authenticated()
                                .antMatchers("/v1/accounts/**").authenticated()
                                .antMatchers("/v1/balance").authenticated()
                                .antMatchers("/v1/loan").authenticated()
                                .antMatchers("/v1/card").authenticated()
                                .antMatchers("/v1/contact").permitAll()
                                .antMatchers("/v1/notice").permitAll()
                                .and().formLogin()
                                .and().httpBasic();
        }


    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
            corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
            corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
            corsConfiguration.setAllowCredentials(true);
            source.registerCorsConfiguration("/**", corsConfiguration);
            return source;
    }

//...........
  • Now, go to the browser with address http://localhost:8000 then open network tool and console on your browser, you will see the error as below.
1
2
3
4
5
Access to fetch at 'http://localhost:8080/v1/contact' from origin
'http://localhost:8000' 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.
  • Then, if you look into the response of the preflight request, you will see there are no headers for CORS.

 #zoom

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
HTTP/1.1 403
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
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
Transfer-Encoding: chunked
Date: Sun, 26 Jun 2022 09:20:51 GMT
Keep-Alive: timeout=60
Connection: keep-alive
  • So, it means that when the browser execute a script to call to another domain (http://localhost:8080) and the server of this domain has not been configured to response some required headers of CORS in the preflight request so the browser has blocked this request by default CORS policy.
  • Now, let's go back to our spring security application and apply the CORS configuration and start the spring security application again.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//...........

    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and().authorizeRequests()
                .antMatchers("/v1/user").authenticated()
                .antMatchers("/v1/accounts/**").authenticated()
                .antMatchers("/v1/balance").authenticated()
                .antMatchers("/v1/loan").authenticated()
                .antMatchers("/v1/card").authenticated()
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

//...........
  • Now, let's refresh http://localhost:8000 on your browser, you will see the error has been disappear and you can call the api successfully.
  • Then, if you look into the response headers from spring security application in the preflight request, you will see there are some headers of CORS configuration.

 #zoom

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:8000
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Credentials: true
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-Length: 0
Date: Sun, 26 Jun 2022 09:06:16 GMT
Keep-Alive: timeout=60
Connection: keep-alive

 #zoom

See Also#

References#