This guide walks you through the process of building a Spring boot 2 application that uses Spring Security and Spring Data JPA. Applying the new way to configure Spring Security without the WebSecurityConfigurerAdapter
Check this tutorial on my Blog π
You will build a Spring Boot application with Spring Security basic authentication and Spring Data Jpa for managing the users.
- A favorite text editor or IDE
- JDK 1.8 or later
- Gradle 4+ or Maven 3.2+
-
Navigate to https://start.spring.io
-
define the project name example:
spring-boot-security-basic-auth
-
Choose Project Maven and the language Java.
-
Choose Your Java version ex: 17
-
Click add dependencies and select:
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database
- Lombok
-
Click Generate.
Unzip the Downloaded Zip and open the Project using your favorite text editor or IDE
Define the User Entity, implement the UserDetails interface and override the implemented methods
@Entity(name = "users")
@Getter
@Setter
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
@Column(unique = true)
private String username;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Define the User Repository and the query method findOneByUsername
Lean more about query methods here
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findOneByUsername(String username);
}
Define the User Details Service implementation that will manage user authentication.
public class CustomUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findOneByUsername(username).orElseThrow(() -> {
throw new UsernameNotFoundException(username);
});
}
}
Define the Spring Security Configuration, You will notice that we didn't extend the WebSecurityConfigurerAdapter we just define beans at this level
@Configuration
public class SecurityConfiguration {
/**
* Define the password encoder.
*
* @return {@link BCryptPasswordEncoder}
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Define the user service to retrieve and authenticate the user.
*
* @param userRepository
* @return {@link UserDetails}
*/
@Bean
CustomUserDetailsService customUserDetailsService(UserRepository userRepository) {
return new CustomUserDetailsService(userRepository);
}
/**
* Define the filer chain.
*
* @param http
* @return {@link SecurityFilterChain}
* @throws Exception
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
(authz) -> authz.antMatchers("/secured").authenticated().anyRequest().permitAll())
.httpBasic();
return http.build();
}
}
Define the Rest Controller with some routes
@RestController
public class AppResource {
@GetMapping("/**")
public String any() {
return "Hello World";
}
@GetMapping("/secured")
public String secured(Authentication auth) {
User user = ((User) auth.getPrincipal());
return "Hello " + user.getFirstName() + " " + user.getLastName();
}
}
Create data.sql
under the resources folder to populate our table
INSERT INTO USERS (FIRST_NAME, LAST_NAME, USERNAME, PASSWORD) VALUES
-- password is '123'
('john', 'doe', 'admin', '$2a$12$vl2OJQMtIZutojuJVhaRXuLmkyFV1QgE24HqFPWoYTmPOXa6wOUbi');
When running the application, by default the data.sql
will be executed before the entity creation into the database, to prevent that add the following property under the application.properties
spring.jpa.defer-datasource-initialization=true
Write some test cases to check that the uri /secured is working as expected
- Test 1 : check with correct credentials
- Test 2 : check with wrong credentials
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SpringBootSecurityBasicAuthApplicationTests {
private final String USERNAME = "admin";
private final String PASSWORD = "123";
private final String WRONG_PASSWORD = "bvfsdg";
@Autowired
TestRestTemplate template;
@Test
public void accessPrivateResourceSuccess() throws Exception {
ResponseEntity<String> response = template.withBasicAuth(USERNAME, PASSWORD)
.getForEntity("/secured", String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
}
@Test
public void accessPrivateResourceFaildGivingWrongPassword() throws Exception {
ResponseEntity<String> response = template.withBasicAuth(USERNAME, WRONG_PASSWORD)
.getForEntity("/secured", String.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
}
Run the Java application as a SpringBootApplication
with your IDE or use the following command line
./mvnw spring-boot:run
Now, you can open the URL below on your browser, default port is 8080
you can set it under the application.properties
http://localhost:8080/
When you access the secured URI /secured, a prompt shows up pass in the username & password
- username : admin
- password : 123
http://localhost:8080/secured
Congratulations π ! You've created a Spring Security application with basic authentication using Spring Boot 2 & JPA
Check new tutorials on my Blog π