Spring Security (for dummies) — EN version

Louis Jeanne-Julien
14 min readMay 27, 2021

Click here for Fr version

If you are a Java developper, chances are that you went through projects implementing Spring Security : the famous framework praised by many people, which as beginners, we hope not to have to configure.

Having been struggling with this framework while following tutorials (I know, real dummy here…), I decided to spend whatever amount of time I needed to gain a deeper understanding on how Spring Security works and how it fits in a web project. It is in fact not trickier than any other framework, but one should not decide to tread its implementation lightly just to tick one more “best practice” in a project.

Keywords and basic concepts

Spring Security does not secure networks or computers, but applications : it is involved in the dialogue between the application and the user (or between two applications). This dialogue is managed by the Spring Dispatcher Servlet, which redirects http requests to the controller classes of the application. Concisely, the role of Spring Security is to insert operations in this interaction, thanks to a bunch of Servlet Filters. This group of filters is the Filter Chain of Spring Security.

The filter chain deals with 2 fundamental concepts :

  • authentication : the user must be identified by a username/password combination.
  • authorizations : users are not equal regarding the operations they are allowed to do. As an example, a user which is not administrator should not be allowed to modify the account of another user.

The filter chain performs actions before the Dispatcher Servlet is reached, in order to check wether a request comes from an authenticated and authorized user, before letting it being processed by the controllers.

Spring Security in action

The error label may be confusing : 401-unauthorized is actually returned if the user is not authenticated, and 403-forbidden is returned if the user is authenticated but does not have the required authorization.

Default Filter Chain

When launching a Spring Security implementing application, one of the first logs appearing in the console looks like :

2020–02–25 10:24:27.875 INFO 11116 — — [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request […]

This log lists the default filters implemented by Spring Security. Hereafter is an ordered list of the filters constituting the default filter chain, with a quick explanation of their action (Spring Security v5.4.2). This list aims at breaking the percieved complexity of Spring Security, but you won’t need to read it at all to understand the rest of the article.

  • WebAsyncManagerIntegrationFilter : this guy is like the cement between the SecurityContext and the WebAsyncManager, allowing to populate the SecurityContext for each request.
  • SecurityContextPersistenceFilter : sends informations from the SecurityContextRepository to the SecurityContextHolder, the latter being necessary for the authentication process, which requires a valid SecurityContext.
  • HeaderWriterFilter : adds headers to the current request. This Filter is particularly useful for preventing certain common attacks by adding headers like X-Frame-Options, X-XSS-Protection and X-Content-Type-Options (see the part about general protection below).
  • CsrfFilter : adds protection against Cross-Site Request Forgery attacks, by involving a token, which is usually saved as a cookie in the HttpSession. It is common to call this filter before any request that may change the state of the application (mainly POST, PUT, DELETE and sometimes OPTIONS).
  • LogoutFilter : manages the sign-out process by calling multiple lougoutHandlers in charge of clearing the security context, invalidating the user session, redirecting to the default page…
  • UsernamePasswordAuthenticationFilter : analyzes an authentication form submission, which must provide a username and a password. This filter is activated by default on the URL /login.
  • DefaultLoginPageGeneratingFilter : builds a default authentication page, unless being explicitely unactivated. This filter is the reason why a login page shows up when implementing Spring Security, even before the developper builds a custom one.
  • DefaultLogoutPageGeneratingFilter : builds a default logout page, unless being explicitely unactivated.
  • BasicAuthenticationFilter : checks if a request includes a basic auth header, and attempts to sign in with the username & password read from this header.
  • RequestCacheAwareFilter : checks in the cache if the current request is similar to an old one in order to quicken its processing.
  • SecurityContextHolderAwareRequestFilter : offers multiple functionalities for each request, particularly concerning the authentication process : getting the user informations, checking if they are authenticated (and if not, offering the possibility to authenticate via the Authentication Manager), getting their roles, offering to sign out via the logout handlers, maintaining the Security Context across multiple threads…
  • AnonymousAuthenticationFilter : provides an Authentication object to the Security Context Holder if there is none.
  • SessionManagementFilter : checks wether a user is authenticated since the beginning of the request, and in that case, proceeds to session related verifications (as an example, checks wether multiple concurrent logins are currently in use).
  • ExceptionTranslationFilter : Manage the AccessDeniedException and AuthenticationException raised by the filterChain. This filter is essential for the GUI to remain consistent when errors occur, for it is the bridge between Java exceptions and http responses.
  • FilterSecurityInterceptor : checks wether the user’s roles match the authorization requirements for the current request.

This filters list is the core of Spring Security, no more, no less. It implements the authentication and authorization concepts, and offers some additionnal features aiming at preventing the common attacks (see the part about general protection below). The developper just needs to configure how these filters should be used in the application : which URLs/methods to protect, which database entries to use for authentication/authorization…

Focus on authentication

Three authentication scenarios must be identified:

  1. The users data (id & password) are saved in a database accessible to the developper. This case is the most common, and will be used as an example in this section.
  2. The application cannot directly reach those informations and has to hand the authentication process to a third party REST service. This is for example the case for an Atlassian Crowd authentication. Basically, this case is similar to the first one, with an added third party layer.
  3. OAuth2 authentication (for example “login with Google”). This scenario needs more knowledge than I have, and won’t be detailed here.

Considering that passwords are encrypted in the database (even the utmost laziness does not exempt you from this restriction), 2 beans must be declared for the authentication to be operational. One of them must implement the UserDetailsService interface, and the other must be a PasswordEncoder.

  • UserDetailsService : the implementation must override a method, which returns a UserDetails object from a simple user id. This object contains at least the user’s username and password, and usually their roles. It is of course possible to use/extends the implementations provided by Spring Security.
  • PasswordEncoder : this bean configures which algorithm to use for the passwords encryption. The Spring Security default encryption is BCrypt. It is possible to use multiple algorithms for the multiple types of users, but this option will not be detailed here.

The declaration of the beans is simple :

Annoted bean declarations

The authentication is build from these 2 beans thanks to the Basic Authentication Filter mentionned in the section above:

  1. The username & password are fetched from a request header, populated thanks to a login form (this step does not require any other action from the developper or the user).
  2. The UserDetailsService bean fetches from the database the mandatory informations to return a UserDetails object, from the username provided by the user. This object contains the saved encrypted password.
  3. The password provided from the login form is encrypted and compared to the password contained in the UserDetails object. The user is correctly authenticated if the encrypted passwords are the same.

It is essential to remember that a proof of authentication is needed for each request if you do not want to be denied the access to your API (unless you decide not to require any authentication, but then, why would you implement Spring Security?). That is why an authentication token (commonly a JWT token) is usually saved in the client and sent back with each request to the rest API. That point used to drive me mad before I read about this subject, when I thought the authentication was managed by the internal magic of Spring Security.

Focus on authorization

Spring Security uses two authorization related objects :

  1. Authorities : basically, an Authority is just a String which designates a responsibility : “user”, “ADMIN”, “GreatDictatorOfDoom”…
  2. Roles : a Role is nothing more than an Authority which begins with “ROLE_”

But then, why are they different? When should we use one or the other? Well, there is no important difference, and it is perfectly fine to use one or the other (or even a combination of both) in any situation. With that being said, generally speaking, a Role corresponds to an Authority which is quite “standard”, “common”, “normalized” : it is common to see a “ROLE_ADMIN” Role (corresponding to the “ADMIN” Authority), but an Authority will be generally preferred for a more unusual title like “Mother_of_the_dragons_of_chaos_of_doom_of_the_thousand_graves_of_destiny_of_death”.

The String corresponding to an authority is wrapped in an instance of the GrantedAuthority interface. The most commonly used implementation of this interface is SimpleGrantedAuthority, but others exist, providing an easy adaptation to the different types of authorizations we may encounter (LdapAuthority, OAuth2UserAuthority, or SwitchUserGrantedAuthority for example). Two parameters must be taken into account in the way to save the authorizations for each user :

  • the number of users
  • the number of Authority/Roles

That is why regarding the application, different strategies are applied : adding a column to the user table, creating a whole different table… From now on, I will consider the authorizations are saved in a column of the users table. The task of fetching the authorizations from a user and mapping them to instances of SimpleGrantedAuthority for Spring Security is usually given to the userDetailsService.

Practical authentication and authorization

Configuring authentication with UserDetails… check.
Saving/fetching authorizations for each user… check.
Now, all we have to do is telling Spring Security which URLs to secure, and which restrictions to apply : some pages will be accessible to anybody wether they are logged in or not, some other will be accessible only to logged in users, and some will be accessible for logged in users which are granted particular authorizations.

The class in which these details are configured must match the following criteria :

  • it extends WebSecurityConfigurerAdapter, which implies it overrides (among others) the configure(HttpSecurity) method.
  • it is annotated with @Configuration, which indicates that the main goal of the class is the declaration and instanciation of Spring beans.
  • it is also annotated with @EnableWebSecurity, which in combination with @Configuration, is the way to tell Spring Security to get its configuration from this very WebSecurityConfigurerAdapter.
Example of configuration class

In the configure(HttpSecurity) method, establishing the restrictions on our URLs is made by using Spring DSL (Domain Specific Language), which is easily readable :

Security configuration method on multiple URLs
  1. http : it is the HttpSecurity object provided in the arguments, which we want to configure.
  2. authorizeRequests : the starting point of requests configuration. The targeted URLs are identified (in this example) by “antMatchers”. An antMatcher describe a string pattern according to the following rules :
    ? = any not null character
    * = any character (null included)
    ** = any number of directories in the URL
    {varName:[a-z]+} = regex [a-z]+, which is saved in the variable named “varName”.
    It is important to keep in mind that the following configurations order is important : the URL will apply the restrictions corresponding to the first match.
  3. hasRole(“ADMIN”) : the targeted URL is accessible to an authenticated user, which is granted the “ADMIN” role, corresponding to the string “ROLE_ADMIN”. A hasRole(“ADMIN”) restriction is strictly equivalent to a hasAuthority(“ROLE_ADMIN”) restriction.
  4. hasAuthority(“PRESQUE_ADMIN”) : the targeted URL is accessible to an authenticated user, which is granted the PRESQUE_ADMIN authorization, corresponding to the string “PRESQUE_ADMIN”.
  5. hasAnyAuthority(“ROLE_ADMIN”,“PRESQUE_ADMIN”) : the targeted URL is accessible to an authenticated user, which is granted one of the following authorizations :
    PRESQUE_ADMIN (string “PRESQUE_ADMIN”)
    ROLE_ADMIN (string “ROLE_ADMIN”)
    A hasAuthority(“ROLE_ADMIN”) restriction is strictly equivalent to a hasRole(“ADMIN”) restriction.
  6. authenticated : the targeted URL is accessible to any authenticated user, without any consideration to their roles/authorizations.
  7. permitAll : the targeted URL is accessible to anyone, authenticated or not (the authentication page is often the only page accessible to unauthentified users).
  8. anyRequest : defines the default behavior of any URL that does not match the preceding rules.
  9. and : requests configuration is over, but we want to configure other aspects of Spring Security (see 10 and 11).
  10. formLogin : authentication is allowed via login form submission.
  11. httpBasic : authentication is allowed via reading a BasicAuth header.

To conclude with configuration, it is useful to know about the “access” method, which allows when associated with Spring Expression Language (SpEL) to best customize in just one instruction the restrictions to some URLs :

Utilisation of SpEL with the access() method

In the example above, the access() method tells Spring Security to:

  • check wether the user is authenticated and has the “ADMIN” role
  • check wether the IP address from which the request is sent is 192.168.1.0/24
  • send the “adminName” variable taken from the l’URL to the checkAdminName method from the monBeanPerso custom class, in order to perform more custom controls.

Defense in depth

Until now, we only talked about web pages access and security, which is sufficient in most of the cases. A way to secure a web app even more is to use “defense in depth”, which is just adding a control before granting access to the methods located in your @Controllers, @Components, @Services, @Repositories, and other Spring beans. This approach is simply implemented by putting annotations on the public methods of said beans. But first, we need to enable it in the @Configuration anotated class (about which we talked in the previous part), by adding @EnableGlobalMethodSecurity :

EnableGlobalMethodSecurity configuration
  • prePostEnabled : enables @PreAuthorize and @PostAuthorize annotations (see examples hereafter).
  • securedEnabled : enables @Secured annotation (see examples hereafter).
  • jsr250Enabled : enables @RolesAllowed annotation (see examples hereafter).

So, what do these annotations do ?

@Secured annotation
@RolesAllowed annotation

The annotated method is only accessible to authenticated users which are granted the “ADMIN” role. Those 2 annotations effects are the same, and accept a role or an authorization as a parameter. The difference between the two is that @Secured is a Spring annotation, provided by the spring-security-core dependency, while @RolesAllowed is a standard annotation provided by javax.annotation-api.

@PreAuthorize annotation

@PreAuthorize and @PostAuthorize annotations are more versatile than the two others, since they can take any SpEL expression, a role, or an authorization as a parameter. @PreAuthorize performs the control before entering the method, while @PostAuthorize performs is after the method has been processed, offering the possibility to modify the result.

A few words about protection against common exploits

1. CSRF

Some requests may modify the state of the application (they are then qualified with the term “stateful”) : they generally correspond to the key-words POST, PUT, DELETE and OPTIONS. These requests are sometimes maliciously sent to the web app from an unrelated tab, in such a way that they are unnoticeable by the user. It would we disappointing to have your account modified or deleted without being aware of it, wouldn’t it? In order to avoid that type of attack, one may use Spring Security CsrfFilter (see the description in the Filter Chain part), by adding the csrf() method to the web security configuration :

Adding csrf filter to the web security configuration

One should note that when developping an app based on an API and a Rest client, csrf configuration is a little bit trickier : the token provided by the API and having the role of certificate of authenticity must be saved and sent back with each request from the client to the API (see an example here).

2. Headers

Beside CSRF, Spring Security provides protections against the most common types of attack. Those protections come mainly from two parameters :

  • the CORS policy that let you decide which origins are allowed to send requests to your API
  • the Response-headers, the default implementation of which being considered best practice. It is nevertheless possible to customize their effects according to the requirements of the web app.

Hereafter is a list of the main Response-headers and their effects :

  • cache control : Spring Security default policy is to avoid saving data in cache. It helps preventing anyone from accessing a confidential page, by, for example, using the login of a co-worker who would have forgotten to lock their session. In fact, around 60% of business targetting attacks actually come from the inside, so this policy is quite important. The headers responsible for controlling the cache are the following :

If the web app uses custom headers in charge of managing the cache, then the ones provided by Spring Security are automatically disabled.

  • content type options : browsers were initially configured to improve user’s experience by infering automatically some resources format (images, texts, etc…) if it was not provided. Why is it a problem? Let’s say someone posts on a blog/forum/etc… some text that is also a valid Js script : the Js code may be executed if the browser just assumes it is not a simple post. Spring Security unactivates automatic format detection by adding the following header :
  • http strict transport security : forgetting to write “https” in a browser request, even if the website redirects automatically to an https address, makes you vulnerable to a “man in the middle” attack. Spring Security provides a header that automatically adds “https” to a selected list of websites :
  • x-frame-options : authorizing a website to be included in a frame may make it vulnerable to ClickJacking. ClickJacking is making a user click somewhere they did not intend to. If a website page is included into a frame, it is possible to use some CSS to hide a button on which a user would click without even knowing it. A way to avoid clickJacking is to disable the rendering of frames content. That is how Spring Security prevents clickJacking, by adding the following header :
  • x-xss-protection : default browser behavior varies when detecting and blocking an XSS (Cross Site Scripting) attack. Some browsers attempt to modify the code in order for the rendered content to look as unchanged as possible, while some others just block the content. Spring Security chooses the latter, by adding the following header :
  • clear-site-data : Clear Site Data is a process in charge of clearing browser-scope data (like cookies). Implementing a header like the following is a good practice for logout requests :

Conclusion

Learning how to implement Spring Security is never finished : there is always something else to know. An exhaustive knowledge of the subject is nevertheless not required to provide decent security to your application. Here are the 3 bullet points you want to memorize, if nothing else :

  • Spring Security is mostly a sequence of filters to use (or not) and to configure.
  • Security provided by Spring Security is based on the authentication / authorization duo.
  • The filters and URL protections are configured in the @EnableWebSecurity annotated class : that is the class to read in order to quickly understand how Spring Security is implemented in an application.

Sources :

Spring Documentation :

Articles & blogs :

Contact infos :

Any question, or nead for more details? do not hesitate to send me a mail at louis.jeannejulien@mail.com

--

--