In this article, we will take a bird's eye view of Spring Security to understand what it is used for and what it has to offer us. Anything on the web is a potential victim of an attack. Unfortunately, securing a web application and implementing features like authorization and authentication are no walk in the park in a world where even the richest, most innovative technology companies get hacked. So when our boss gives us a task to secure the application, should we sit and wait, scared, not knowing what to do? Of course not.
Whether or not you are going to use Auth0 to secure your Spring app, you will need to know the basics of Spring Security to secure your application rapidly, and this makes it a must-know framework for any Spring developer. The thing with Spring Security is: It is difficult. Not because it is poorly designed or could be easier to use, but because of the complexity of its domain: Application security. Complex problems require technically sophisticated solutions, and security is one of them.
If you have ever taken a glance at a Spring Security-related documentation or tutorial, you might be scared of how complicated it looks. I mean, take a look at this picture from Wikipedia:
It is very natural to feel overwhelmed, especially if you are also new to Spring. Spring Security is a huge framework, but once you get the basics down, you can easily get the task of securing your app done by integrating it with an IDaaS service like Auth0 without the hassle of implementing everything yourself.
Why Spring?
Spring is the most popular Java framework for developing web services. Whether it is an enterprise project or a startup, Spring has all the tools to help you develop rapidly and focus on your application logic.
- Very reliable and mature platform (around since 2003) with a huge ecosystem. This means there are plenty of ready-to-use libraries and projects to make your life easier.
- Used extensively by almost every big name in techs such as Amazon, Google, and Netflix.
- It is fast & multithreaded (unlike Node.js), therefore great for CPU-intensive tasks such as video encoding, image manipulation, financial calculations.
- Lets you focus on the business logic with very minimal configuration via the help of Spring Boot.
- Supports Kotlin.
- JPA generates most of the basic queries for you. Only for the special cases, it is useful to write a query by yourself.
- Easy to develop Microservices with an embedded web server and Spring Cloud.
- Secure. Spring is commonly used for banking and other security-critical applications, partially due to Spring Security.
What is Spring Security?
Spring Security is a framework that focuses on providing authentication and authorization mechanisms to Spring applications. It was started in 2003 as an open-source project under the name of "Acegi Security" before officially being included in Spring Projects. In addition to authentication and authorization, Spring Security can be configured to protect your application against many common attacks, including but not limited to CSRF, XSS, Brute Forcing, and MITM (Man in the Middle), if you want to learn more about these attacks, you can take a look into this article by Holly Lloyd.
Why Spring Security?
One of the best things you can do if you don't have experience in securing an application is to find out if the language/platform you are using has a security framework. By using a security framework that can be relied on, we are delegating the responsibility of determining the architecture & implementing the core security features to a team of experts in this field who work on that particular framework. If you are building a Spring application, Spring Security is a reliable, extensively tested, and open-source security framework, and it is probably one of the most reliable security frameworks among every language and platform.
Features
In this section, we are going to direct our attention to the features that Spring Security has and what it can provide us or its use-cases. We are also briefly mentioning the "how" while doing so.
Password Encoding
The benefits of Spring Security are not limited to helping us with Authentication and Authorization. It can also help us to apply best practices in saving users, a.k.a, building a sign-up feature.
In the process of building a sign-up feature, there are some common mistakes that could result in severe security issues.
One of those mistakes is, saving the user passwords in plaintext without any kind of hashing. This not only allows the hackers who may gain access to your server to view the passwords directly but also opens your users to the threat of getting their other accounts cracked as well since the majority of people use the same passwords for all their accounts.
Another common mistake is trying to reinvent the wheel and create your own encryption/hashing algorithm instead of using the well-known, tested, proven algorithms.
Spring Security allows us to assign a secure password encoder to our UserDetails object to prevent these mistakes. By default, it uses BCrypt to encrypt the passwords, which is considered a well-rounded algorithm for encoding passwords. It is also possible to set the number of hashing rounds (or the strength as the parameter name suggests) and the secure random algorithm implementation to be used in the process.
BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion version, int strength, java.security.SecureRandom random)
It would not be surprising to encounter code snippets like this in a configuration class that extends WebSecurityConfigurerAdapter:
@Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); }
In-Memory Authentication
In-Memory Authentication means using a database that stays in application memory/RAM (one example is the h2 database) to save users and perform the authentication without saving them to a persistent database. Such databases can be useful when you are building a proof-of-concept app or prototyping your application and need to get going or test your authentication code without bothering yourself with a database. An in-memory database is also useful if you run your tests without mocks so that your real database will remain untouched and the changes caused by tests will be in temporary memory.
Spring Security supports authentication of users who reside in the in-memory databases.
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // Some other configuration code here... @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("temporary").password("temp123").roles("ADMIN"); } }
LDAP Authentication
LDAP is short for Lightweight Directory Access Protocol, and it is commonly used for authenticating the user accounts of employees in enterprises. It allows you to specify users/user-groups in a hierarchical structure and define their permissions.
dn: cn=John Doe,dc=example,dc=com cn: John Doe givenName: John sn: Doe telephoneNumber: +1 888 555 6789 telephoneNumber: +1 888 555 1232 mail: john@example.com manager: cn=Barbara Doe,dc=example,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: top
Spring Security has a project called "spring-security-ldap" that allows us to use LDAP Authentication in our Spring apps. Spring Boot also provides auto-configuration for an embedded LDAP server to save us from the difficult task of setting up an LDAP authentication server. You can take a look at this official tutorial for more information.
Session Management
In the applications where backend and frontend are tightly coupled, like Spring MVC applications or any other MVC framework: Whenever a user logs in, a session is stored in the server that holds information about this user. Operations like retrieving, caching, destroying this session are called Session Management. Spring Security provides mechanisms to control this session object. It will create the session when a user logs in, destroy it when they log out and allow us to set the timeout value.
Spring Security also takes additional measures to make sure the sessions are being utilized in a secure way:
- It is configurable to disable using URL rewriting to avoid the Session Tracking attack.
- It automatically migrates the session when the user logs in again to avoid Session Fixation.
- It allows us to use httpOnly and secure flags on session cookies to protect our cookies.
Remember Me Authentication
Remember Me authentication is the mechanism to recognize the user after their session times out, so the users do not need to enter their credentials every time they visit the website. Spring Security supports multiple ways to implement this type of authentication.
The typical way to implement Remember Me authentication is by hashing the user details with a secret key that is on the server and encoding it along with the username and expiration time.
base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" password + ":" + key)) username: As identifiable to the UserDetailsService password: That matches the one in the retrieved UserDetails expirationTime: The date and time when the remember-me token expires, expressed in milliseconds key: A private key to prevent modification of the remember-me token
Though it works, this approach has some flaws. If an attacker captures the cookie somehow, it will work by anyone from any source until it expires. The only way for the users to invalidate their tokens is to change their passwords.
A better approach for implementing Remember Me authentication is storing a persistent token in the database instead of the Remember Me cookie. The token is stored in a similar way to a session object, but the difference is that it is saved to a database instead of temporary application memory.
API Security with JWT (JSON Web Tokens)
We have mentioned that server-side sessions can be used to confirm the identity of a user and to keep track of their roles (what they are allowed to do). Since the REST is a sessionless protocol, it is advised not to store a session in RESTful APIs. We can instead make use of another popular approach to check if a user is authorized to take some action.
In applications that use JWT, after a user completes a successful login, an access token is generated with a secret key on the server, which usually contains information about the user's identity and token's generation timestamp. In separated frontend-backend architectures, the frontend stores this token in cookies.
It is possible to implement JWT Authorization for your application by making use of Auth0 JWT library for encoding/decoding the tokens. In addition to this, Spring Security may be used for filtering the requests and checking for the user's roles, only allowing the authorized users to pass through the filters.
OAuth 2.0
Open Authorization 2.0 is an open standard for checking a user's permissions on service with the help of an authorization server. It is not uncommon to use OpenID Connect to confirm the user's identity, and OAuth can then provide a token that contains the list of permissions for this user. After the user receives the OAuth 2 access token, they will use it to access the protected resources on the server. OAuth is a very popular standard, and it is also used to implement features such as Facebook login or Google account login, so even if you do not know what it is consciously, you probably used it before without knowing.
Setting up an authorization server and implementing OAuth and OpenID Connect comes with its own risks and can be time-consuming. It is easy to mess up and create a vulnerability by mistake.
Instead of trying to build everything from scratch, it is a good idea to integrate Spring Security with Auth0 and spend your precious time on improving the business logic instead. You can check out this awesome tutorial on securing your RESTful API with the help of Auth0 by Tadej to secure your API rapidly.
Building Blocks of Spring Security
In this section, we will take a look at several concepts which are heavily applied in Spring Security. Basic knowledge about these topics will serve as a foundation that we can build upon since all these topics are more or less connected to each other.
Chain of Responsibility
Chain of Responsibility is a GoF behavioral design pattern that aims to decrease the tight coupling between request handlers and the sender. The "chain" refers to a list of ordered request handlers, just like a
LinkedList
structure, and the request handler can be a method or a class that implements some Handler
interface. The responsibility is just a fancy word of answering the request, each handler on the chain can either return a response (by taking the responsibility) or pass the request to the next handler on the chain (delegating the responsibility).Filters
Before we start talking about filters, it is helpful to familiarize ourselves with Interceptors: An Interceptor is a class that implements the
HandlerInterceptor
interface and intercepts the web requests before they hit the controller. Its name is very descriptive of what it actually does, and a Filter can be thought of as a more specialized version of an Interceptor.A Filter method runs after the request hits the Tomcat (or any other) web server and before its arrival to the servlet. This allows Filters to even be able to block the request from reaching the actual servlet, explaining why they are called Filters. There are many
Filter
interfaces in Spring Security that can be implemented to create a customized filter class. We could then tell Spring Security to add these Filters we created to its "Filter Chain."If you are already wondering whether this "Filter Chain" has something to do with our Chain of Responsibility pattern, congratulations to you! That is why we started by learning the Chain of Responsibility pattern. Before we even add a single custom Filter, Spring Security will usually have around six filter chains by default (source), and they will start filtering out the requests as soon as we add Spring Security as a dependency.
Here is the sample code for a JWT Authorization Filter:
public class JWTAuthorizationFilter extends BasicAuthenticationFilter { public JWTAuthorizationFilter(AuthenticationManager authManager) { super(authManager); } @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_STRING); if (header == null || !header.startsWith(TOKEN_PREFIX)) { chain.doFilter(req, res); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(req); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(req, res); } }
You do not need to understand all of this but just glance over this
doFilterInternal()
method. It takes a FilterChain
as its parameter and what it does is: Get the header from the request, check if it has a token and if it does not have it, pass it down the filter chain by calling:chain.doFilter(req, res);
This is a pattern you will encounter often. Notice that the last line of this method is also the same.
Matchers
"Filters are nice and all, but they apply to every request as soon as I add them to my security configuration, and what if I want to apply a filter only to a single REST resource?" you might ask. This is when URL Matchers should come to the scene. URL Matchers in Spring Security are called Ant Matchers, historically named after Apache Ant build system, and they allow us to specify a regex-like matcher to determine which endpoints should be subject to filtering.
Example configuration:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(HttpMethod.GET, "/public/**").permitAll() .anyRequest().authenticated() .and() .addFilter(new CustomFilter(authenticationManager()) .addFilter(new JWTAuthorizationFilter(authenticationManager())) }
This code snippet allows all GET requests to URLs that start with "/public/" to bypass the filters. For any other request, the API consumer should be authenticated, and the custom filters will also apply. Code similar to this can be found in a Configuration class.
User Roles (Role-based Authorization)
Now that we know about Ant Matchers, we are able to specify the paths that filters will be applied to, but we still lack some flexibility to define role-specific permissions. For example, we could want an endpoint to be accessible only by users who have the role ADMIN or any arbitrary set of roles. By using role-based authorization/authentication, we can achieve such behavior. I will not give the whole code or a tutorial for implementing role-based security since it would be the topic for a whole new article by itself but you should know that Spring Security allows you to define roles for your users and apply filters depending on those roles as follows:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").permitAll .antMatchers("/new-blog-post").hasAnyAuthority("ADMIN", "AUTH0 EMPLOYEE", "GUEST_WRITER") .antMatchers("/edit/**").hasAnyAuthority("ADMIN", "EDITOR") .antMatchers("/delete/**").hasAuthority("ADMIN") .and() .formLogin().permitAll() .and() .logout().permitAll(); }
This could be the configuration for Auth0 blog permissions if it was built with Spring Security.
Conclusion
In this article, we have started by defining Spring Security and tried to provide insights about what kind of things a security framework provides. I hope it is more clear after reading the features section and seeing the example use cases for Spring Security.
After learning what it is good for, we have taken a look at some important concepts that can help us understand Spring Security better.
In the future, we are also planning to add on this introduction with more advanced tutorials such as:
- More detailed Spring Security Tutorials
- Spring Boot Authentication Tutorial: Web App
- Securing Spring Microservices
- Secure Spring Serverless Development