Secure development with Spring Boot
Ákos Jakab (Software Engineer, Avatao)

Over the past decade, Spring Framework has became a well established and prominent web framework for developing Java applications. The most exciting and essential changes in the Spring ecosystem has been the birth and progression of Spring Boot. No matter what you need, Spring Boot provides comprehensive, easy-to-use, and interdisciplinary development environment tools for deployment and supports the entire development lifecycle.
Why Spring Boot?
One of the strongest features of the Spring Boot is its auto-configuration capability which attempts to automatically configure the Spring application based on the .jar dependencies we have added. This key feature related to the Spring Boot starters gives us freedom, guarantees quick development time, and saves us from the burden of specific metadata configuration. However, applying security to our application is a perfect example when this auto-configuration capability is not enough as the out-of-the-box spring security configuration usually requires fine-tuning. The Spring Security starter is a one-size-fits-all solution, and there are many details that need to be specified. First, we have to override the basic auto-configuration and define our custom-tailored security requirements based on the available classes on the classpath.
Spring Security is a powerful and highly customizable authentication and access-control framework. In the following examples, we will following examples, the most useful Spring Security features and best practices.
The magical WebSecurityConfigurerAdapter
By implementing a security configuration class, we can, among other things, override the default security settings, specify permissions, and define users, roles, or a custom login page view. For this, we have to extend the WebSecurityConfigurerAdapter
. By default, the following configuration is set up in the WebSecurityConfigurerAdapter
class. This grants authenticated users access to all URLs.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/users/**")
.hasRole("USER")//USER role can access /users/**
.antMatchers("/admin/**")
.("hasRole('ROLE_ADMIN')") //ADMIN role can access /admin/**
.and()
.formLogin() //enabling formlogin
.loginPage("/login") //deafult login page URL
.failureUrl("/login?error") //deafult login error URL
.usernameParameter("username") // default parameter name of username
.passwordParameter("password") // default parameter name of password
.and()
.logout()
.logoutSuccessUrl("/login?logout") // redirect URL after logging out
}
}
Using HTTPS
To use HTTPS, we need an SSL certificate. With Spring Boot we can enable HTTPS with a generated self-signed certificate for testing purposes. In the development phase, there is no easier way to get a certificate. However, to go public, we need publicly signed certificates to verify the service provider’s authenticity. As such, it’s not recommended to use a self-signed certificate in production. To go public, you will need an SSL (Secure Sockets Layer) certificate from a Certificate Authority. There are some free certificate providers but their support isn’t typically comprehensive. The most reliable and free certificate provider is Let’s Encrypt. It is high quality, widely known and frequently used.
In the long run, it won’t be sufficient but to get started in development, you can generate a self-signed certificate with a certificate management utility called keytool. Use this command:
keytool -genkeypair -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
This will generate a PKCS12 keystore with a generated certificate, with certificate alias tomcat. Put your generated keystore.p12
file into your project’s resources directory and add these lines to your application.properties:
server.port = 8888
# Automatically blocking requests coming from http
security.require-ssl = true
server.ssl.key-store = classpath:keystore.p12
server.ssl.key-alias = tomcat
server.ssl.key-store-password = yourpassword
server.ssl.key-store-type = PKCS12
If you already have your SSL certificate, you can import it to a keystore to enable HTTPS in your Spring Boot app.
With this command, you can also create a new keystore containing your certificate:
keytool -import -alias tomcat -file myCertificate.crt -keystore keystore.p12 -storepass password
The awesome built-in CSRF protection
You might be familiar with OWASP’s definition of CSRF: „Cross-Site Request Forgery is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing.”
As Adam Barth et al. wrote, there are three widely used techniques for defending against CSRF attacks:
- validating a secret request token: the most popular CSRF defense is including a secret token with each request and validating the received token is correctly bound to the user’s session. This prevents CSRF by forcing the attacker to guess the token of the session.
- validating the HTTP Referer header is the simplest defense that prevents CSRF by accepting requests only from a trusted source
- validating custom headers attached to XMLHttpRequests: sites can be defended against CSRF with a custom header set via XMLHttpRequest. Make sure the header is present before processing state-modifying requests.
As Adam Barth et al. conclude in their article, there are three widely used techniques for defending from CSRF attacks but none of these techniques are satisfactory on their own.
Spring Boot can prevent a CSRF attack. The CSRF protection is active by default, so we do not have to configure it explicitly. If you use Thymeleaf, the CSRF token will automatically be added as a hidden input field. If you are using JSP you have to add it to the form by yourself:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
While not recommended, you are able to disable CSRF protection. It is best to use POST
requests instead of GET
to pass CSRF tokens, since Spring does not protect GET
methods by default. This is a general requirement for proper CSRF prevention. For more details, visit the CSRF Spring Documentation.
There are several different CSRF protection types that Spring offers:
Stateful CSRF protection
In this case, the backend generates a token that is sent to the user’s browser and which that has to be copied to an HTTP header before a request to an API endpoint is made.
The server has to compare the token appearing in the header with the one it sent to the browser. If the tokens are the same, then it is accepted. The reason this works is because a malicious website is not able to read the browser’s cookie that contains the CSRF-protection token and copy it to the HTTP header.
Stateless CSRF protection
If maintaining the state for CSRF token on the server side is problematic, it is still possible to check that the tokens are the same in order to make sure the API request is not a CSRF attack. The client’s browser generates a (cryptographically strong) pseudorandom value and sets it as a cookie on the user’s side, separately from the session identifier. This value is sent with the request to the backend API as a hidden form value or another request parameter or header. If the values are not the same, the server will reject the request. This method of protection can be implemented by creating a new filter.
Samesite cookie attribute
Definition by OWASP: “SameSite prevents the browser from sending the cookie along with cross-site requests. The main goal is mitigating the risk of cross-origin information leakage. It also provides some protection against cross-site request forgery attacks.”
Possible values for the flag are:
- Lax: with this value defined the server maintains the user’s logged-in session after the user arrives from an external link
- Strict: it will prevent the browser from sending the cookie to the target site in all cross-site browsing context
In Spring Security you can easily do this with a filter:
public class SameSiteFilter extends GenericFilterBean {
@Override
public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response
resp.setHeader("Set-Cookie", "HttpOnly; SameSite=strict"
chain.doFilter(request, response
}
}
Then add this filter to your SecurityConfig class:
http.addFilterAfter(new SameSiteFilter(), BasicAuthenticationFilter.class)
Although Spring Security provides a comprehensive toolkit to prevent the most common types of CSRF attacks, it can also be tricked with subdomain takeover and man-in-the-middle attack because the tokens are not tied to the user identity and are not signed. To mitigate this risk, do not expose an HTTP endpoint, only HTTPS and do not allow external content on subdomains.
With Avatao’s platform, you can gain a deeper understanding of the CSRF protection.
Method-level Authorization
Sometimes we need to secure methods with Java configuration. Using @EnableGlobalMethodSecurity
annotation we can ensure the security of the service layer. By defining roles, we can assure that users can only execute or trigger specific methods.
For method level authorization, we first need:
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
//...
}
prePostEnabled
is for enabling Spring Security pre and post annotations. With securedEnabled
, we can enable the @Secured
annotation. The jsr250Enabled
property allows us to use the @RoleAllowed annotation
.
public interface FlightService {
List<Flight> findAll
@Secured("ROLE_ADMIN")
void updateFlight(Flight flight
@Secured({ "ROLE_USER", "ROLE_ADMIN" })
void makeReservationForFlight(Flight flight
}
We can define a list of security configuration attributes for service methods with @Secured
annotation. If anyone tries to invoke a method and does not possess the required roles/permissions, an AccessDenied
exception will be thrown. In the above example, anyone with an ADMIN
role can invoke the updateFlight
method. For makeReservationForFlight
method both USER
or ADMIN
roles are appropriate.
Encoding sensitive data
Storing passwords as plain text is perhaps one of the worst security practices and is forbidden by Spring Security by default. There are several encoding mechanisms Spring Security supports, like SHA256, SHA512, PBKDF2, BCrypt, and SCrypt. In our example, we will use BCrypt, one of the best available solutions. The PasswordEncoder
interface has only two methods: encodePassword
and isPasswordValid
.
You can encode the password during authentication by defining a PasswordEncoder
bean in the security configuration class:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//...
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(11
}
//...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(
auth.getDefaultUserDetailsService()).passwordEncoder(encoder());
}
}
The Spring provides a simple solution to encode the password in the database. Let’s see how to store a user with encoded password:
import org.springframework.security.crypto.password.PasswordEncoder
//...
@Autowired PasswordEncoder passwordEncoder
//...
User john = new User("john", passwordEncoder.encode("johnny1"
List<User> users = new ArrayList<>();
users.add(john
userRepository.save(users
Avoiding XSS Attacks
When an XSS (Cross-Site Scripting) attack is performed, a malicious script is injected into a website usually by using an input field to send the malicious code. While there are a wide range of XSS attack vectors, most can be avoided by choosing proper web frameworks, input validators/filters/encoders and appropriate configurations such as Content Security Policy (CSP).
Spring Security allows users to efficiently inject the default security headers.
Current Default Security Headers provided by Spring Security include:
- Cache-Control
- Content-Type Options
- HTTP Strict Transport Security
- X-Frame-Options
- X-XSS-Protection
With using Spring configuration, all default security headers are set.
Avoiding clickjacking
Clickjacking is a technique of tricking a web-user into clicking on something other than what they expected. This – usually malicious – click is triggering an action in a hidden or transparent iframe. For example, a logged in user in a banking system might click on a button that gives access to someone else. Allowing your website to be shown in an iframe can create a new attack vector for clickjacking.
There are several ways to avoid clickjacking actions. Even though you can use frame breaking code, a more modern approach is suggested to avoid clickjacking with the X-Frame-Options header set to DENY
. Luckily, Spring Security automatically sets the X-Frame-Options to DENY
, thus whenever you need to change it, you will need to do so it explicitly.
We can also selectively enable, disable or define the sameorigin
option with Java annotations, adding the required header:
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin(); // or disable()
}
}
Here are available options for policy:
DENY
This is the default value. The page cannot be displayed in a frame, regardless of the site attempting to do so.SAMEORIGIN
The page can be displayed in a frame on the same origin as the page itselfALLOW-FROM
It is possible to define an origin, where the page can be displayed in a frame
Share this post on social media!
Related Articles
Deserialization vulnerabilities in Java
Understanding serialization and deserialization vulnerabilities is the first step toward building secure applications. For most developers, it’s a challenge to find the right balance between coding securely and meeting other objectives like tight deadlines. This often results in products that are vulnerable to deserialization attacks which would be otherwise difficult to stage.
JWT handling best practices
The purpose of this post is to present one of the most popular authorization manager open standards JWT. It goes into depth about what JWT is, how it works, why it is secure, and what the most common security pitfalls are.
Ruby needs security
Every year, Ruby is becoming more and more popular thanks to its elegance, simplicity, and readability. Security, however, is an issue we can’t afford to neglect.
