Securing your application with Spring Security
It has been about 2 months since last post, and Project 1 is ready for demo showcase! As you may remember, it’s meant to be commercialized, but it’s also a statement of my technical skills, passion for programming and constant drive to self-development. Therefore, I’d like to limit the possibility of using the webapp to people, which contacted me in order to hire me and would like to see the proof of my skills. Spring Security is a perfect tool to do the job and I’ll cover such scenario in this post.
Spring Security, as its name suggests, is all about securing your application. It’s one of the framework’s most known project, implemented using AOP and servlet filters. You can do a whole lot of things with it, just to name a few:
- authorize users with sources ranging from in-memory storage, throughout LDAP, to relational and non-relational databases
- secure pages or specific elements which builds up the page (think of a button which allows to delete users comments and is only visible to logged-in admin)
- secure application endpoints by using advanced filtering options (like allow to perform POST operations from controller A only to logged-in users)
- automatically add CSRF token to forms.
Enough talking, let’s skip to the meat. To start, we need to add Spring Security to the classpath. The most convenient way to do so is throughout extending Maven’s dependencies with Spring Boot Security Starter:
If you run the application now, every request (be it a page or endpoint) will be intercepted and you’ll be asked to enter your credentials to proceed. The funny things is, there’s no users source provided which implies there is no such login data that allows to proceed. That’s how the default implementation of WebSecurityConfigurerAdapter
’s configure(HttpSecurity)
method works. There’re some things we have to do to make use of it. Let’s start with providing user’s database.
While in-memory authentication is sufficient in case of development environment, in production you’ll mostly lookup database. If you’ve read my previous post, you know that I’ve choosen non-relational database to persist P1’s data. In case of users I’ll do the same. It’s also a little bit more challenging as Spring Security provides support for relational databases as well as LDAP out of the box. In case of non-relational database, you’ll have to implement UserDetailsService
interface yourself. Before we’ll reach it, there are several classes which have to be coded.
The first one is User
which is a MongoDB’s document representing persisted user entities. In my use case, my goal is to only store the basic informations that allows for user identification, that is user’s name and password. If you take a look at UserDetailsService
interface documentation, you’ll notice that it’s one and only method, which is loadUserByUsername(String)
returns objects of type UserDetails
, which is also an interface. To avoid wrapping up, we’d like our User
class to implement this interface upfront. We consult ourself with docs once again and write something like this:
User
class is self-explanatory but you’d probably like to have a look at getAuthorities()
implementation. While we store authorities (roles, in other words, like ROLE_USER
, ROLE_ADMIN
and so on) as a list of String
s, we have to return a class that extends GrantedAuthority
, which is achieved with SimpleGrantedAuthority
constructor.
Next: we have to somehow retrieve our User
s from MongoDB. That’s achieved with this simple UserRepository
implementation:
Once again, if you find any of these topics unclear, I encourage you to read this post. The next step is providing a controller, which allows admin to add new users, alongside with DTO class for data transfer; it’s very simple, consisting of only three fields (username
, password
, grantedAuthoritiesList
), so I won’t attach it. Here’s code of UserRepositoryController
:
Nothing surprising here. Basically, we provide two endpoints, one capable of adding to the database a User
identified by username
and password
(we don’t do hashing here, but you should in case of production environment), with a ROLE_USER
(see two-param constructor of User
class above) and another which allows us to additionaly define its authorities.
Now, the most important class, which provides Spring Security configuration. That’s the single point of app’s behaviour definition. Let’s take a look on a SecurityConfig
class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/welcome.html").permitAll()
.antMatchers("/deleteAll/\*\*", "/addUser", "addUserWithRoles").hasAuthority(ConstProvider.ADMIN_ROLE)
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic().and()
.csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
}
Similar to any other Spring configuration file, this one is annoted with @Configuration
. There is also another annotation, namely @EnableWebSecurity
. Some older sources may tell you to use @EnableWebMvcSecurity
if you’d like Spring Security to integrate with Spring MVC, but as of Spring Security 4.0, @EnableWebMvcSecurity
is deprecated and @EnableWebSecurity
is the single one which will also handle MVC.
6th line is responsible for injecting UserDetailsService
. We’re programming on an interface here, which benefits us in loose coupling of beans: the huge advantage here is application’s flexibility, achieved by replacing concrete bean implementation with another one. @Autowired
resolves bean by name - the default is class name started with lowercase letter. Of course, it’s up to programmer to make the bean “visible” to the application context - we’ve achieved this with @Service
annotation on top of UserService
class. To make our configuration benefit from users source, we override the implementaion of configure(AuthenticationManagerBuilder)
(line 23).
The crucial part of the code is overriden method configure(HttpSecurity)
. In 12th line it says that we allow anyone to display our welcome page. Next line states that three endpoints can only be reached by a user with ROLE_ADMIN
authority. Following line says that remaining requests can be done only by authenticated (logged-in) users (without defining their exact role). I’ve also decided to switch off CSRF for the conveniency of development - you can see it in line number 18.
That’s basically all you need to know to allow to your webapp only those you’d like to. It’s highly possible that you’d like to tune the visuals of login page to meet your expectations. You can start by taking a look at its default implementation and adding your HTML and CSS to it. It’s mapped to /login
, so make sure your custom file is resolved correctly.
Hope you liked this post and see you next time!
Leave a comment