User Authentication with Spring Security and MariaDB

People learning Spring for the first time will find that certain tutorials will only get them so far. Spring Security is one of those tutorials. For example, most tutorials will show how to set up a basic security configuration with a deeply problematic `InMemoryUserDetailsService` with hard-coded usernames, passwords and authorities or roles. This approach also comes with a deprecation warning that will remind developers that it is insecure to use this approach in production. Okay fine. But why are we building secure websites that are not secure in production? That seems futile. What do we do when our bosses actually want us to do good work?
Database-ready Spring Boot Security
This tutorial is going to assume you know a few things about Spring:
- That you understand how to set up a project using the Spring Initializr or via an IDE like VS Code or Eclipse.
- That you know the basic of Spring Boot Application Design including how to set up a Controller and Spring Boot Application.
- You have reasonable in-depth knowledge of a Spring UI approach such as with Thymeleaf or Vaadin.
- Familiarity with a data source of your choice (we will use MariaDB as the example in this tutorial).
- Removing boilerplate java code via Lombok.
- In sum, this is not a beginner tutorial. We are going to look at production-ready Spring.
With that in mind, I recommend including the following dependencies to your project:
- Spring Boot Security
- Spring Data Java Persistence
- Spring boot starter web
- Spring boot dev tools
- A database driver (we will use MariaDB)
The first thing we need to do is set up our database. In addition, I would like to assume that we will want to include a default username and password for our project in our `application.properties` file. We find this file under `/my_project/src/main/resources` in a typical package. Note that your spring project will not work if no database exists at the url localhost:3306/my_db_name.
spring.datasource.url=jdbc:mariadb://localhost:3306/my_db_name
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
# This setting means that our project will drop all the tables
# every time we run. Great for prototyping.
spring.jpa.hibername.ddl-auto=create-drop
# We have metadata to support these in another file.
my-project.default-username=default-username
my-project.default-password=default-password
In addition to the application.properties settings, we also want to let Spring and anyone using our project to know that we made-up some project settings. We do this in a metadata file under /myproject/src/main/resources/META-INF/additional-spring-configuration-metadata.json. It looks like this:
{"properties": [{
"name": "my-project.default-password",
"type": "java.lang.String",
"description": "project default password"},
{ "name" : "my-project.default-username",
"type" : "java.lang.String",
"description": "project default username"}]}
Now that our project is set up with JPA, a database driver and (hopefully) working configuration information. Now its time to set up our database for Spring security. In Spring Security, we will need to create a UserDetailsManager that will allow us to review who has permission to what. In the beginner tutorial, you did this by using a User.withDefaultPasswordEncoder() call that included usernames, password and roles. In our case, we need to ask Spring to pull that information up from a database. For this, we will use the JdbcUserDetailsManager() call. This class extends another one called JdbcDaoImpl() whose documentation informs us of how we should set up our database to accommodate Spring Security. It says we need two tables:
- Users : with columns for username, enabled and password.
- Authorities: with columns for a username and an authority.
That seems pretty easy, but there are some catches! Authorities will need to accommodate a many to many relationship. There are multiple ways to do this. I am going to do it the hard way because it’s the most illustrative.
@Entity
@Table(name="users")
// Using Lombok as an alternative to filling out getters and setters
@Getter @Setter @NoArgsConstructor
public class User {
@Id
private String username;
private String password;
// Need to specify that we want MariaDB's TinyInt(1) approach to Bools.
@Column(nullable = false, columnDefinition = "BOOLEAN")
private Boolean enabled;
}Easy enough user table. How about authorities? You could use @OneToMany entry in the Users class, but let’s just build that out so we can understand things better — especially how to build a composite class in Spring JPA.
@Entity
@Table(name = "authorities")
@Getter @Setter @NoArgsConstructor
@IdClass(AuthorityId.class)
public class Authorities {
@Id
String username;
@Id
String authority;
}Unfortunately, this is not enough to get us through our day. You will notice that the IdClass is saying we have another class that will guide us towards what we will need for a composite class. Let’s build that now.
@EqualsAndHashCode
public class AuthorityId {
private String username;
private String authority;
public AuthorityId(String username, String authority) {
this.username = username;
this.authority = authority;
}
}Notice that we need the @EqualsAndHashCode implementations so we can use this class as a combined identifier class. When you run the project, good old Spring JPA will create tables for you. Now we want to add our first user. While this is good enough for now, you may also want to connect this user identity to another class so you can get other important information like email address, full name, an avatar and so on. For this, you could just do the following:
@Entity
// Might as well add an all arguments constructor and a builder
// so you can build a profile user on the fly
@Setter @Getter @NoArgsConstructor @AllArgsConstructor @Builder
public class UserProfile {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@OneToOne
private User user;
private String firstName
// last name, email, etc.
}Now we can go back to our Security Configuration tutorial.
@Configuration
// you could decide to include your properties in another spot
@PropertySource("classpath:application.properties")
@EnableWebSecurity
public class Security Config extends WebSecurityConfigurerAdapter { // you could extend to use VaadinWebSecurity
// if you wish
// get database information from Spring JPA
@Autowired
private DataSource dataSource;
// use Spring's Value annotation to get data from application.properties
@Value("${my-project.default-password}")
private String default_password;
@Value("${my-project.default-username}")
private String default_username;
@Override
protected void configure(HttpSecurity http){
http.authorizeHttpRequests()
.requestMatchers(new AntPathRequestMatcher("/")).permitAll();
super.configure(http);
// if you decide to use Vaadin.
// setLoginView(http, LoginView.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
// for encoding our passwords.
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
// This is all we really need to do.
JdbcUserDetailsManager dbManager = new JdbcUserDetailsManager(dataSource);
// But we would like to have a default super-user, so let's create one:
dbManager.createUser(
User
.withUsername(default_username)
.password(passwordEncoder().encode(default_password)
// give all the powers!
.roles("USER", "ADMIN", "SUPER_ADMIN")
.build()
);
return dbManager;
}
} Once you start your project, you should be able to see your new user inside your database. Spring security will also allow you to login with all the associated permissions. You may want to explore VaadinWebSecurity to show how to allow different users with different roles access different kinds of content.
That’s all there is to it! Hopefully there is enough here to help you move forward on the more exciting business logic things you hope to do with your newly secured web application.
