Spring Boot – Load users from database

This is the third article in a series on authentication with Spring Boot.
In the first article we authenticated using social networks, and allowed any user to access our application.
In the second article we used inMemoryAuthentication for users that used the login form. In essence, we hardcoded our users.
This article is about adding users to a database. We are not going to allow users to sign up, we’re just going to add the users manually.

Setup Postgres

For our user entity, we want to save the following fields:

  • username
  • password (optional)
  • role
  • email
  • name

You can add the clientIds for the social networks that you allow your users to connect with for extra security. But we won’t do that here.
But we do want to be the email to be unique. Every user must have his own email propperty.

CREATE TABLE public."user"
(
    user_name text NOT NULL,
    password text,
    role text NOT NULL,
    email text NOT NULL,
    name text,
	UNIQUE(email)
)

To test this our login later, we need to add a user. The password is BCrypt encoded for “password”.

insert into "user"
(user_name, password, role, email, name)
values
('user','$2a$10$bXetyuwpEai6LomSykjZAuQ5mxU8WqhMBXGuWYnxlveCySRlGxh2i', 'USER', 'test@example.com', 'Test User')

JPA Database access

Once we have the database in place, it would be nice to actually use it in our code. To do this, we need to configure Spring to connect to our database, create a representation of the database in our code, and create a Repository that glues it together.
First, the configuration. Since we created the database schema ourselves, we don’t want Hibernate to do that. You could set spring.jpa.hibernate.ddl-auto to ‘validate’ to make sure the schema matches your model. Also, we want to show the SQL it is executing, so it’s easier to see what’s wrong. You want to turn this off when you’re done, because it generates a lot of logging.

spring:
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
  datasource:
    url: jdbc:postgresql://localhost:5432/database
    username: databaseuser
    password: password

For the model we’re going to use Lombok, so we don’t have to deal with the boilerplate code of getters and setters. We are using the Java Persistance API to handle the database mapping.

@Getter
@Setter
@Entity
@Table(name="user", schema = "public")
public class User {

    @Id
    private String email;

    @Column(name="user_name")
    private String userName;
    private String name;
    private String password;
    private String role;
}

We use a Repository to get the User objects from the database. Spring will do a lot of magic for us, so we only need to specify the JPA query and a method in an interface to retrieve the data.

@Repository
public interface UserRepository extends CrudRepository<User, String> {

    @Query("SELECT u FROM User u WHERE u.userName = :username")
    User getUserByUsername(String username);

    @Query("SELECT u FROM User u WHERE u.email = :email")
    User getUserByEmail(String email);
}

UserDetails and UserDetailsService

At this point we need to have UserDetails, and a service to get them from the database. Since we’re using bot FormLogin and OAuth2, I’ve decided to implement both UserDetails and OAuth2User in the same class. This makes things easier later on.

@Repository
public class MyUserDetails implements UserDetails, OAuth2User {

    private final User user;

    public MyUserDetails(User user){
        this.user = user;
    }

    @Override
    public Map<String, Object> getAttributes() {
        return Collections.emptyMap();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());
        return Collections.singletonList(authority);
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String getName() {
        return user.getName();
    }
}

The service tries to load a user by username, and throws an exception when no user with that username could be found.

@Component
public class MyUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Autowired
    public MyUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = userRepository.getUserByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException("Could not find user");
        }

        return new MyUserDetails(user);
    }
}

Update FormLogin configuration

Previously we configured in-memory authentication. Now that we have all pieces in place to retrieve our users from the database, we need to configure it.
We need to configure the UserDetailsService.

@Bean
public UserDetailsService userDetailsService(){
	return new MyUserDetailsService(userRepository);
}

Then we’ll configure a DaoAuthenticationProvider using the UserDetailService. The passwordEncoder was already configured in the last blogpost.

@Bean
public DaoAuthenticationProvider authenticationProvider() {
	DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
	authProvider.setUserDetailsService(userDetailsService());
	authProvider.setPasswordEncoder(passwordEncoder());
	return authProvider;
}

And then to tie it toghether, we use this AuthenticationProvider as the source of the users for the LoginForm

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	auth.authenticationProvider(authenticationProvider());
}

Update OAuth configuration

Since we now use the database to store our users, we also need to update the OAuth configuration. We need to verify whether the user who’s trying to login using OAuth is actually known to us. The key information that we can use here is the email address. That’s why there is a method to get the user by email address in the repository, which we are going to use here. If there is no user with the email address that was found in the OAuth2 principle, we throw an exception. Otherwise, we return that user.

@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
	DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
	return request -> {
		OAuth2User auth2User = delegate.loadUser(request);
		String email = auth2User.getAttribute("email");
		User user = userRepository.getUserByEmail(email);
		if (user != null){
			return new MyUserDetails(user);
		}
		throw new InternalAuthenticationServiceException("User not registered");
	};
}

Update WebController and frontend

Now that we have all the pieces in place to use the database for user verification, we want to use this information on our site. Since there will be problems with some login attempts (maybe the user misspelled his username), we would like to be able to show an error message on the login page. So we update the /login endpoint like this:

@RequestMapping(value = "/login")
public String login(HttpServletRequest request, Model model){
	if (request.getSession().getAttribute("error.message")!= null) {
		String errorMessage = request.getSession().getAttribute("error.message").toString();
		log.info("Error message: "+errorMessage);
		model.addAttribute("errormessage", errorMessage);
	}
	return "login";
}

On the login page, we need to add the following to display this error message:

<div class="alert alert-danger" role="alert" th:if="${errormessage}">
    <span id="user" th:text="${errormessage}"></span>
</div>

In other places we would like to get the user’s name. To do this, we need to get the principal from the authentication token.

private Optional<MyUserDetails> extractMyUserDetails(Principal principal){
	if (principal instanceof UsernamePasswordAuthenticationToken) {
		return Optional.of((MyUserDetails) ((UsernamePasswordAuthenticationToken) principal).getPrincipal());
	} else if (principal instanceof OAuth2AuthenticationToken){
		return Optional.of((MyUserDetails) ((OAuth2AuthenticationToken) principal).getPrincipal());
	}
	log.severe("Unknown Authentication token type!");
	return Optional.empty();
}

And then we get the username from the MyUserDetails class

@RequestMapping(value = "/welcome")
public String welcome(Principal principal, Model model) {
	MyUserDetails userDetails = extractMyUserDetails(principal)
			.orElseThrow(IllegalStateException::new);
	model.addAttribute("name", userDetails.getName());
	return "welcome";
}

Spring Boot login with a form

Previously I wrote about securing your application with social login. But not everybody has a social account. In this article we’re going to add formlogin to the application. Formlogin simply means that your users can log in with a username and password. We’re going to keep it as simple as possible, with in-memory authentication.

Adding users

Since we’re going to use usernames and passwords to allow users to login, we need to define a password encoder. Spring will not compare the literal password that it receives, but encodes it and then compare the encoded passwords. When the passwords are stored in the database, you don’t want to see them as plain text.

@Bean
public BCryptPasswordEncoder passwordEncoder() {
	return new BCryptPasswordEncoder();
}

The next step is to add the users. We’re going to use inMemoryAuthentication with a single user, to keep it as simple as possible.

public class WebsiteApplication extends WebSecurityConfigurerAdapter {

	// some more code

	protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
				.withUser("user").password(passwordEncoder().encode("password")).roles("USER");
	}
}

Note that we’re using the passwordEncoder defined in the previous step to encode the password here. Also, we need to define a role, even though we’re not using it yet.

Allow formlogin

Now we need to tell Spring Security to allow the use of our login form.

public class WebsiteApplication extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler("/");
		// @formatter:off
		http
			.authorizeRequests(
					a -&gt; a
				.antMatchers("/error", "/webjars/**","/oauth/**","/login").permitAll()
				.anyRequest().authenticated()
			)
			.formLogin(f -&gt; f
					.loginPage("/login").permitAll()
			)
			// some more code
			;
		// @formatter:on
	}
	// some more code
}

Add login form

Now that we have enabled the formlogin in the backend, it’s time to add a login form in the frontend.

<form name='loginForm' th:action="@{/login}" method='POST'>
	<table>
		<tr>
			<td>User:</td>
			<td><input type='text' name='username' value=''></td>
		</tr>
		<tr>
			<td>Password:</td>
			<td><input type='password' name='password' /></td>
		</tr>
        <tr>
            <td><input name="submit" type="submit" value="submit" /></td>
        </tr>
    </table>
</form>

Webcontroller changes

There were two methods that use the authentication information. The first, index(), doesn’t need to change at all. Sure, we get a different Authentication implementation, but it still works unaltered.

@RequestMapping(value = {"/","/index"})
public String index(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
	if (authentication.isAuthenticated()) {
		return "redirect:/welcome";
	}
	return "redirect:/login";
}

The second method, where we try to find the username of the authenticated user does change. Here we drop the @CurrentSecurityContext annotation, and request the Principal directly. However, based on the method of authentication, we need to do something else to get the username. If the user used the login form, we can get the username directly from the principal. When the user authenticated using OAuth2, we need to dig a little deeper.

@RequestMapping(value = "/welcome")
public String welcome(Principal principal, Model model) {
	if (principal instanceof UsernamePasswordAuthenticationToken) {
		model.addAttribute("name", principal.getName());
	} else if (principal instanceof OAuth2AuthenticationToken){
		model.addAttribute("name", ((OAuth2AuthenticationToken) principal).getPrincipal().getAttribute("name"));
	}
	return "welcome";
}

Conclusion

Adding a login form to a Spring Boot application is easy. We’ve hardcoded a user with in memory authentication. Since we’re going to use a password, we need to have a password encoder. We’ve configured Spring Security to allow the user to login using a form. Then we’ve actually added a basic HTML login form, and as a last step we’ve modified the backend to work with a different type of principal.

Apache HttpClient

For a hackaton I wanted to read some files from our BitBucket server. I knew the URLs of the files, but there were some complications. First, you need to be authenticated. According to the documentation, the preferred way of authentication is HTTP Basic Authentication when authenticating over SSL. We are using an SSL connection, but with self-signed certificates.

When working with SSL, Java uses keystores and truststores. The difference between the two is that keystores store private keys, which are used by the server side, and truststores contain public keys, which are used by the client side. We have our own custom truststore, and we can tell the JVM to use that one by passing the following parameters:

-Djavax.net.ssl.trustStore=/truststore/location -Djavax.net.ssl.trustStorePassword=password

This works when you only want to access sites using your custom truststore. As soon as you want to make a connection to public sites, this fails. By default you can only use one truststore at a time. If you want more, you have create some custom code.

Or you can use the Apache HttpClient.

HTTP Basic Authentication

There are a couple of ways to use Basic Authentication. For BitBucket we need to use Preemptive Basic Authentication, which means we need to configure a HttpClientContext.
The first thing we need to do is to setup the CredentialsProvider. This doesn’t need much explanation.

        // Configure CredentialsProvider
        final CredentialsProvider provider = new BasicCredentialsProvider();
        final UsernamePasswordCredentials credentials
                = new UsernamePasswordCredentials("username", "password");
        provider.setCredentials(AuthScope.ANY, credentials);

Next we need to configure an AuthCache. We’re going to cache the authentication for a specific host.

        // configure AuthCache
        final HttpHost targetHost = new HttpHost("host", PORT, "https");
        final AuthCache authCache = new BasicAuthCache();
        authCache.put(targetHost, new BasicScheme());

Last, we use the previous steps to configure our HttpClientContext

        // configure HttpClientContext
        final HttpClientContext context = HttpClientContext.create();
        context.setCredentialsProvider(provider);
        context.setAuthCache(authCache);

HttpClient with custom SSL context

Now it’s time to configure our HttpClient. We’re going to load our truststore specifically for this client. This means that other clients and connections will still use the default Java truststore.

        SSLContext sslContext = new SSLContextBuilder()
                .loadTrustMaterial(
                        new File(configuration.getTruststoreLocation()),
                        configuration.getTruststorePassword().toCharArray()
                ).build();

        SSLConnectionSocketFactory sslSocketFactory = 
                new SSLConnectionSocketFactory(sslContext);
        return HttpClientBuilder.create()
                .setSSLSocketFactory(sslSocketFactory)
                .build();

Using the HttpClient to execute the request

Now that we have configured both the HttpClient and its context, executing a request also becomes easy. Note that we need to pass the context to the execute method.

        final HttpGet request = new HttpGet(link);
        HttpResponse response = httpClient.execute(request, context);
        InputStream connectionDataStream = response.getEntity().getContent();