In this article, we will be examining the concepts of CORS, or cross-origin resource share. We will explore how to enable CORS on a simple Spring Boot RESTful web service using Kotlin.
We will first introduce our sample project, which will be our basis for illustrating the concept of cross-origin resource share. Then we will explore the theory behind CORS, how it works, and why you might need it. Finally, we will modify our sample project to make use of cross-origin resource share, explore some ways issues might arise, and address them.
By the end of this post, you should have an understanding of CORS and a basic implementation of it in a Spring Boot project.
We want to stress that this article depends on having previous knowledge of the Kotlin and Spring Boot development stack. If this is not the case, please check out the Spring Boot guide for some excellent starting material.
Building Our Sample Project
Alright, so before we explore the principles of cross-origin resource share, let's make sure your environment is ready so you can follow along with us. Note: If you prefer to go straight into the theory, please skip to the following section.
We'll start by building our boilerplate web application in Spring Boot. Assuming that you already have your environment ready to work with Java—having the JDK and an IDE to work with—please proceed to the Spring Initializr and set up your project.
Select "Spring Web" as your dependency, either "Gradle" or "Maven" as your dependency manager, and "Kotlin" as your language. We have selected "Gradle" as our dependency manager.
Then proceed to download your project.
After the download finishes, open the project. Once you have done this, we need to add the apache httpclient library for the API to respond to our requests.
To do this in Gradle, just add the following line to the dependencies bracket in the build.gradle file:
testImplementation 'org.apache.httpcomponents:httpclient'
Once that is done, you can run gradle build, and it will pull the dependency into your project.
Now we will add a GET endpoint to serve as our resource representation for a simple request that will respond with a message. To achieve this, create a resource representation class file called "hello.kt" in the src/main/kotlin/com/example/demo folder and input the following code:
package com.example.demo
class Hello {
private var id: Int
private var content: String
fun Hello() {
this@hello.id = -1
this@hello.content = ""
}
fun Hello(id: Int, content: String) {
this@hello.id = id
this@hello.content = content
}
fun getId(): Int {
return id
}
fun getContent(): String {
return content
}
}
Notice that we have added two parameters to receive extra data if necessary.
Finally, we need to add a controller to our Spring Boot to handle the requests. You can proceed to create a HelloController.kt file in the same directory and add the following code:
package com.example.demo
import java.util.concurrent.atomic.AtomicLong
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@RestController
class HelloController {
private val template: String = "Hello, %s!"
private val counter: AtomicInt = AtomicInt()
@GetMapping("/hello")
public fun hello(@RequestParam name: String = "World"): Hello {
System.out.println("==== get greeting ====")
return Hello(counter.incrementAndGet(), String.format(template, name))
}
}
That's all you need.
You can proceed to this Spring Boot tutorial page for more information on this particular example and the complete code.
Understanding CORS
OK, so now that we have our project set up, let's talk about CORS.
CORS, or cross-origin resource share, is a browser policy devised to control what resources the browser was allowed to retrieve from other origins. It essentially serves as a safelist that prevents malicious actors from exploiting vulnerabilities on web applications that would open users to exploits.
The way the server informs the browser of the CORS policy is through an HTTP header that's included in all responses. This header contains a list of all domains that the browser is allowed to make requests of and retrieve resources from on the client side.
By default, browsers forbid making requests to domains other than the intended domain, given the potential for exploitation. This is particularly true considering the large number of requests performed on most modern websites.
Furthermore, this behavior serves as a vast net protecting users from running code loaded from potentially malicious origins.
A basic CORS header would look something like this:
Access-Control-Allow-Origin: https://web.example
You could also specify more properties and restrictions regarding the allowed methods to use, the headers, and the policy max-age.
Access-Control-Allow-Origin: https://web.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-TEST, Content-Type
Access-Control-Max-Age: 96000
If you want to dive deeper into the particulars of cross-origin resource share, we strongly encourage you to read our article on CORS, where we go into way more detail and focus on practical examples of the technology.
Enabling CORS in Spring Boot
There are two ways to enable CORS on a Spring Boot application: make changes to individual controllers, or make a general, global implementation. We will be implementing both, so you can choose which works best for you.
First, let's implement it on an individual controller.
To enable cross-origin access in our endpoint, we first have to add the @CrossOrigin directive on top of our controller method as follows:
@RestController
class HelloController {
private val template: String = "Hello, %s!"
private val counter: AtomicInt = AtomicInt()
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/hello")
public func hello(@RequestParam name: String = "World"): Hello {
System.out.println("==== get greeting ====")
return Hello(counter.incrementAndGet(), String.format(template, name))
}
}
Notice that we specify the origins as a parameter directly on the directive. You can specify one or many origins as an array of strings. Additionally, you can specify a set of parameters corresponding to the predefined CORS rules like methods, maxAge, and allowedHeaders.
Moreover, you can add the directive to the class level to enforce CORS on all endpoints defined in the class itself.
For an application-wide implementation of CORS, we would then instead change the @GetMapping directive to implement a filter.
@RestController
class HelloController {
private val template: String = "Hello, %s!"
private val counter: AtomicInt = AtomicInt()
@GetMapping("/hello-javaconfig")
public fun hello(@RequestParam name: String = "World"): Hello {
System.out.println("==== get greeting ====")
return Hello(counter.incrementAndGet(), String.format(template, name))
}
}
Notice that the only change was in the mapping string.
Then we will go to our main application class, in our case DemoApplication.kt, and add the following WebMVCConfigurer:
@Bean
public fun corsConfigurer(): WebMvcConfigurer {
return WebMvcConfigurer() {
@Override
public fun addCorsMappings(CorsRegistry registry): Void {
registry.addMapping("/hello-javaconfig").allowedOrigins("http://localhost:8080")
}
}
}
Here we can specify our CORS origins and rules accordingly.
Keep in mind that you can use both approaches in tandem. This approach is advantageous if your application requires a more complex and intricate CORS policy.
And that's it. You have a CORS implementation in your hands.
Facing Issues With CORS
Implementing CORS is a relatively straightforward and manageable affair. For the most part, the majority of the information you need is available through the developer tools in your browser of choice. Additionally, tools like Chrome Lighthouse allow you to check your network behavior in your development environment and suggest how to implement these policies.
You might fear breaking your website in the process of adding CORS, but fear not. As long as you proceed on a staging environment and keep an eye on what the network traffic tool tells you, you will be OK. Finding the missing origin or offending rule disturbing your platform's normal flow is quite simple.
Solving CORS issues is a game of finding the culprit and tweaking our directives.
Final Thoughts
Of course, we understand that, for the most part, the modern web necessitates making requests to different origins. This is the staple that allows developers to provide the best and most reliable experience. Many features that we take for granted in this day and age result from the interoperability and tight connection of the web. Therefore, mechanisms like cross-origin resource share have been conceived to satisfy these needs and deliver robust security and stability.
However, sometimes the correct implementation of these policies can be challenging for large-scale platforms. That is why solutions like StackHawk are excellent for providing robust and straightforward solutions to security needs.
StackHawk is a test suite that provides a wide range of testing tools that empower your team's workflow, guaranteeing that your platforms and business are ready for the challenges of the modern web. Be it CORS, Code Injection, or any other vulnerabilities, StackHawk provides exceptional value to your teams. Check it out for yourself.
This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.