Spring Security Basic Authentication With Database

Spring Security Basic Authentication With Database

Authenting users, securying APIs, implementing role access based are all difficult jobs but thankfully we can rely on Spring to solve these problems. In this article we’ll use Spring Security to implement Basic Authentication using a database. Let’s dive into it!

Dependencies

For this project we’ll need the following dependencies:

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>3.3.2</version>
</parent>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-config</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-web</artifactId>
	</dependency>
	<dependency>
		<groupId>com.h2database</groupId>
		<artifactId>h2</artifactId>
	</dependency>
</dependencies>

We’re using H2 as database. It will create the db and generate the tables for us.

Entity Definition

First of all we have to define an entity which will be used to retrive and validate user credentials.

@Entity
@Table(name="user_login")
public class User {

	@Id
	private Integer id;

	private String username;

	private String password;

	// getters and setters
	
}

Repository

Now let’s create the corresponding repository.

public interface UserRepository extends CrudRepository<User, Integer> {

	public Optional<User> findUserByUsername(String username);
	
}

The above method will be used to retrive the user.

Spring Security Configuration

At this point we need to configure Spring Security and implement Basic Authentication. Let’s start by creating the following class.

@Configuration
public class PasswordConfig {

	@Bean
	public PasswordEncoder encoder() {
		return new BCryptPasswordEncoder();
	}
	
}

In the above class we simply release the BCryptPasswordEncoder bean to Spring Context. We’ll use this object to match the password provided by the user to the password we’ll retrive from the findUserByUsername() method we defined earlier. We’re taking for granted that users’ passwords are encoded in the database but this is fine because this is how we’re gonna save users’ password later.

At this point let’s create the class to retrive the user.

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	private final UserRepository userRepo;

	public UserDetailsServiceImpl(UserRepository userRepo, PasswordEncoder encoder) {
		this.userRepo = userRepo;
	}

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		var user = userRepo.findUserByUsername(username)
				.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
		return new User(user.getUsername(), user.getPassword(), new ArrayList<>());
	}

}

The above class is responsible to retrieve the user and return the UserDetails. It’s extending the UserDetailsService which is the interface provided by Spring Security.

Now we need to create the Authentication Provider.

@Component
public class BasicAuthenticationProvider implements AuthenticationProvider {

	private final UserDetailsService userServ;

	private final PasswordEncoder encoder;

	public BasicAuthenticationProvider(UserDetailsService userServ, PasswordEncoder encoder) {
		this.userServ = userServ;
		this.encoder = encoder;
	}

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		String name = authentication.getName();
		String password = authentication.getCredentials().toString();
		try {
			UserDetails user = userServ.loadUserByUsername(name);
			if (comparePasswords(password, user.getPassword())) {
				return new UsernamePasswordAuthenticationToken(name, password, new ArrayList<>());
			}
		} catch (UsernameNotFoundException e) {
			throw new BadCredentialsException("Invalid username or password");
		}
		throw new BadCredentialsException("Invalid username or password");
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return authentication.equals(UsernamePasswordAuthenticationToken.class);
	}

	private boolean comparePasswords(String enteredCred, String userCred) {
		return encoder.matches(enteredCred, userCred);
	}

}

The AuthenticationProvider is an interface provided by Spring Security that allows us to implement our authentication. In this case we’re implementing the basic authentication, but we could implement any. It’s basically the class that holds the logic of our authentication.

As you can see we’re just retriving the user by username and then we’re comparing the passwords using the password encoder. If it matches we return the Authentication object otherwise we throw an exception.

The following class is optional and you may skip it. Its purpose is to remove the browser popup which asks to enter username and password. In many cases this comes in handy but it’s totally optional and up to you.

public class NoPopupBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

	public NoPopupBasicAuthenticationEntryPoint() {
		this.setRealmName("spring-security-basic.auth-db");
	}

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException {
		response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
	}

}

Now we need to add the last piece. Let’s create the SecurityConfig.

@Configuration
public class SecurityConfig {

	private final BasicAuthenticationProvider authProvider;

	public SecurityConfig(BasicAuthenticationProvider authProvider) {
		this.authProvider = authProvider;
	}

	@Bean
	public AuthenticationManager authManager(HttpSecurity http) throws Exception {
		AuthenticationManagerBuilder authenticationManagerBuilder = http
				.getSharedObject(AuthenticationManagerBuilder.class);
		authenticationManagerBuilder.authenticationProvider(authProvider);
		return authenticationManagerBuilder.build();
	}

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http.authenticationProvider(authProvider)
				.sessionManagement((session) -> session.sessionCreationPolicy(STATELESS))
				.authorizeHttpRequests((matcher) -> {
					matcher.requestMatchers("/login.html", "/favicon.ico").permitAll();
					matcher.anyRequest().authenticated();
				}).httpBasic(basic -> {
					basic.authenticationEntryPoint(new NoPopupBasicAuthenticationEntryPoint());
				});
		return http.build();
	}

}

In the authManager method we set our previous authentication provider to the authentication manager, then we release it to the context. The AuthenticationManager provides authentication services to the application.

In the filterChain method we set again the authentication provider, then we make sure our app session is stateless, then we allow free access only to the URLS login.html and favicon.ico while the others will require authentication and lastly we set http basic, making sure the browser won’t shop any popups by using our custom entry point.

Adding a User

In order to test the app we need to have at least one user in our database. Let’s create a class to insert one.

@Component
public class UserInsertHandler {

	private final UserRepository userRepo;

	private final PasswordEncoder encoder;

	public UserInsertHandler(UserRepository userRepo, PasswordEncoder encoder) {
		super();
		this.userRepo = userRepo;
		this.encoder = encoder;
	}

	@PostConstruct
	public void insertUser() {
		var user = new User();
		user.setId(1);
		user.setUsername("user");
		user.setPassword(encoder.encode("userPassword"));
		userRepo.save(user);
	}

}

Protected Resource

Before testing the application there’s one more thing we need to do. Let’s create the controller and expose an endpoint which will be protected by our basic authentication.

@RestController
public class MessageController {
	
    @GetMapping("/messages")
    public Map<String, String> getMessage() {
        return Map.of("message", "Ok");
    }
    
}

Testing the Application

Now we just need to test our app and see if it works properly. Run the app and then open Postman. At this point make a GET request to http://localhost:8080/messages.

Implementing Basic Authentication with Spring Security

We’re getting 401 unathorized because we’re trying to access a protected resource without entering credentials. In Postman, add basic authentication and add “user” in the username field and “userPassword” in the password field. You should get the following response.

Conclusion

In this article we’ve covered various aspects of configuring and customizing Basic Authentication with Spring Security. Basic Authentication is a good starting point to learn the basics and then dive into more advanced authentication methods. Source code is available on GitHub.

Lorenzo Miscoli

Software Developer specialized in creating and designing web applications. I have always loved technology and dreamed of working in the IT world, to make full use of my creativity and realize my ideas.
Scroll to Top