CrossOrigin and CORS in RESTful Web Service

In this short tutorial, I am going to share with you how to enable CrossOrigin in a RESTful Web Service API built with Spring Boot, Spring MVC and which also has Spring Security enabled.

Enable CrossOrigin for Specific Endpoint 

To enable cross origin AJAX HTTP request to a specific RESTful Web Service endpoint in our Rest Controller class we can use @CrossOrigin annotation. For example, the code snippet below will allow AJAX requests being sent from:

  • http://localhost:8080
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping(path = "/email-verification", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
 public OperationResponseModel verifyEmailToken(@RequestParam(value="token") String token) 
 {
     
      boolean isVerified = userService.verifyEmailToken(token);
      
      OperationResponseModel returnValue = new OperationResponseModel();
      returnValue.setOperationName(RequestOperation.VERIFY_EMAIL.name());
     
      if(isVerified)
      {
          returnValue.setMessage("Email address is verified");
          returnValue.setResult(ResponseStatus.SUCCESS.name());
      } else {
          returnValue.setMessage("Email token could not be verified");
          returnValue.setResult(ResponseStatus.ERROR.name());
      }
     
      return returnValue;
 }

In the brackets of @CrossOrigin you need to specify the domain name from which an AJAX HTTP request is sent. In my example I used localhost but of course, it does not need to be a localhost domain name but a remote domain name from which HTTP request is sent.

Please note that if the request is sent from the same domain name but a different port number then you will have to take that in account as well. The following are all different origins:

  • https://appsdeveloperblog.com
  • http://appsdeveloperblog.com
  • http://appsdeveloperblog.com:8080
  • http://appsdeveloperblog.com:8088

Enable Multiple Origins

If you need to enable your RESTful Web Service endpoint to accept AJAX request from multiple domain names but still limit the list, then you need to add all of them to the list of origins in the @CrossOrigin annotation. For example:

@CrossOrigin( origins = { "http://appsdeveloperblog.com", "https://appsdeveloperblog.com", "http://swiftdeveloperblog.com", "https://swiftdeveloperblog.com" } )

Enable All origins for Specific Endpoint

If you need to enable your Web Service Endpoint to accept AJAX HTTP Requests sent from any origin then use the * for the list of origins. For example:

@CrossOrigin( origins = "*" )

Enable CrossOrigin for All Web Service Endpoints

If you need to make all of your Web Service endpoints in a single Rest Controller call to accept AJAX HTTP Requests from different origins, then you can use the @CrossOrigin annotation at the class level. For example:

@CrossOrigin( origins = "*" )
@RestController
@RequestMapping("/users")
public class UserController {

...

}

Once you do that, you can send an AJAX HTTP Request to your RESTful Web Service endpoint from a web app hosted on a different server like this:

Sending CrossOrigin HTTP Request with JavaScript

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<script language="javascript">
    $(document).ready(function () {

        performlogin()

        function performlogin() {
            var settings = {
                "async": true,
                "crossDomain": true,
                "url": "http://localhost:8084/mobile-app-ws/email-verification",
                "method": "POST",
                "headers": {
                    "Content-Type": "application/json",
                    "cache-control": "no-cache"
                },
                "processData": false,
                "data": "{\"token\": \"474hfy8fjsd894jfhgye92osldf84\"}"
            }

            $.ajax(settings).done(function (response) {
                console.log(response);
            });
        }

    });
</script>

Enable Global CORS Configuration

Using @CrossOrigin annotation above the method or a controller class is helpful but there is also a way to create a global CORS configuration.  

Instead of configuring Cross Origin details above the method in a Rest Controller class, create a new WebConfig class which implements WebMvcConfigurer like it is in the example below: 

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
   
    @Override
    public void addCorsMappings(CorsRegistry registry) {
   
       registry
               .addMapping("*")
               .allowedMethods("OPTIONS", "GET", "PUT", "POST", "DELETE")
               .allowedOrigins("*")
               .allowedHeaders("*");

    }
}

Note that to configure allowed Mappings, Origins and Headers I used to tell the framework that all options are allowed. Instead of using *, you can specify specific path mapping or specific headers for example. In the example below I specify specific mapping and specific origins:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
   
    @Override
    public void addCorsMappings(CorsRegistry registry) {

       registry
               .addMapping("/users/email-verification")
               .allowedMethods("OPTIONS", "GET", "PUT", "POST", "DELETE")
               .allowedOrigins("http://www.appsdeveloperblog.com")
               .allowedHeaders("*");
    }
}

Spring Security and CORS

If your RESTful Web Service application has the Spring Security enabled and you need to enable Cross-origin Reference Sharing(CORS), you can do it by:

  1. Enabling the cors on the HTTPSecurity object and
  2. Creating a new Bean and configuring a CorsConfigurationSource like it is in the example below.

Enable CORS on the HTTPSecurity object

In the Java class that has the WebSecurity configuration and which is annotated with @EnableWebSecurity annotation enable CORS at is it in the example below:

 @Override
    protected void configure(HttpSecurity http) throws Exception {


            http.cors().and()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
                .permitAll()
...

                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
 
    }

once you have the cors() enabled, add a new method which will return a CorsConfigurationSource:

@Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
   
        configuration.setAllowedOrigins(ImmutableList.of("*"));
        configuration.setAllowedMethods(ImmutableList.of("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

If you do not want to allow all origins, then specify a list of origins this way:

configuration.setAllowedOrigins(ImmutableList.of("http://localhost:8080","http://localhost:8084"));

Below is my complete Web Security Configuration class:

package com.appsdeveloperblog.app.ws.security;

import com.appsdeveloperblog.app.ws.service.UserService;
import com.google.common.collect.ImmutableList;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private final UserService userDetailsService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurity(UserService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.cors().and().csrf().disable().
                authorizeRequests()
                .antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
                .permitAll()
                .antMatchers(HttpMethod.GET, SecurityConstants.VERIFICATION_EMAIL_URL)
                .permitAll()
                .antMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_REQUEST_URL)
                .permitAll()         
                .antMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_URL)
                .permitAll() 
                .antMatchers("/h2-console/**")
                .permitAll()
                .antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**")
                .permitAll()
                .anyRequest().authenticated().and()
                .addFilter( new AuthenticationFilter(authenticationManager()) )
                .addFilter( new AuthorizationFilter( authenticationManager() ))
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
 
        http.headers().frameOptions().disable();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }
 
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        //configuration.setAllowedOrigins(ImmutableList.of("http://localhost:8080","http://localhost:8084"));
        configuration.setAllowedOrigins(ImmutableList.of("*"));
        configuration.setAllowedMethods(ImmutableList.of("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
 
}

I hope this short example will help you add a CrossOrigin support to your RESTful Web Service API. If you are interested in learning more about building RESTful Web Service APIs, have a look at the below list of video courses and hopefully one of them will be exactly what you needed.
The code examples used in this tutorial I took from my video course which demonstrates how to implement most of the commonly needed functionality like:

  • User sign-up,
  • User sign-in,
  • Email verification feature,
  • Get list of users and pagination,
  • Update and Delete user details,
  • and other useful functionality.

Have a look at the complete list of video lessons here.