SPRING SECURITY DEFINITION
Spring Security is a lightweight security framework that provides authentication and authorization support to secure Spring applications. It comes with implementations of popular security algorithms.
This article guides us through the process of creating a simple connection example to an application with Spring Boot, Spring Security, Spring Data JPA and MYSQL.
APPLICATION CONFIGURATION
Let’s begin with a very basic application (in terms of installation requirements) that starts a Spring application context. Two tools that will help us with this are Maven and Spring Boot. I will be skipping lines that are not particularly interesting like the Maven repository configuration. You can find the complete code on GitHub.
To start off we will add the following dependency to the pom of our application.
1. <dependency> 2. <groupId>org.springframework.boot</groupId> 3. <artifactId>spring-boot-starter-security</artifactId> 4. </dependency>
At the start of our application we can see the following information in the console:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.4.RELEASE) 2018-09-10 22:44:09.059 INFO 645 --- [ main] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: c657aef6-758a-409d-ac02-814ff4df55be
Spring Boot therefore provides us with a default password.
We have just added Spring Security to our application’s classpath, Spring sets a default configuration, and asks a username and a password to access our application.
So to access our application with the Spring default configuration, we enter the username as username and the default password provided by Spring, displayed in the console at the start of our application (here c657aef6-758a-409d-ac02-814ff4df55be) in the authentication form.
In the rest of this article, we will be customizing this configuration.
CREATING USER ENTITY
We are going to create the User.java class, this class implements UserDetails interface.
The interface provides information about the primary user. Implementations are not used directly by Spring Security in a matter of security purposes. They simply store user information that is then encapsulated in authentication objects. This makes it possible to store non-security related information (such as e-mail addresses, etc.) in an appropriate location.
@Entity @Table(name = "USER") public class User implements Serializable , UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer userId; private String username; private String password; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { return false; } public void setUsername(String username) { this.username = username; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Below are the feed scripts from our table in the H2 embedded database. The script is placed in the data.sql file, the scripts located in the data.sql file are executed each time we start our application.
INSERT INTO USER(username, password) VALUES('ADMIN', 'ADMIN')
IMPLEMENTATION OF APPENHENTICATIONPROVIDER CLASS
Spring Security provides a variety of options in order to perform authentication. All of these options follow a simple contract. AuthenticationProvider processes an authentication request and a fully authenticated object with full credentials is returned.
The standard and most common implementation is the DaoAuthenticationProvider – which retrieves user details from a simple read-only DAO user – the UserDetailsService. This detail user service can only access the user name to retrieve the complete user entity and in most cases, this is sufficient.
public class AppAuthProvider extends DaoAuthenticationProvider { @Autowired UserService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; String name = auth.getName(); String password = auth.getCredentials() .toString(); UserDetails user = userDetailsService.loadUserByUsername(name); if (user == null) { throw new BadCredentialsException("Username/Password does not match for " + auth.getPrincipal()); } return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return true; } }
IMPLEMENTATION OF THE REPOSITORY USERREPOSITORY
We are going to create a UserRepository.java class, this class inherits the JpaRepository class. The function findUserWithName (String name) retrieves a User by his username.
public interface UserRepository extends JpaRepository<User, Integer> { @Query(" select u from User u " + " where u.username = ?1") Optional<User> findUserWithName(String username); }
IMPLEMENTATION OF THE USERSERVICE SERVICE
We are going to create the UserService.java class, this class implements a UserDetailsService interface. The UserDetailsService interface is used to retrieve the data related to the user. It uses a method called loadUserByUsername that finds a user based on the user name and can be overridden to customize the user’s search process. It is used by DaoAuthenticationProvider and loads details about the user during authentication.
@Service @Slf4j public class UserService implements UserDetailsService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Objects.requireNonNull(username); User user = userRepository.findUserWithName(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); return user; } }
IMPLEMENTATION OF SECURITYCONFIG CONFIGURATION CLASS
To customize the configuration we will create a class that inherits from WebSecurityConfigurerAdaptater. This class must have @EnableWebSecurity and @Configuration notes. Configuration classes are scanned when the application starts.
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userDetailsService; @Autowired private AccessDeniedHandler accessDeniedHandler; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf() .disable() .exceptionHandling() .authenticationEntryPoint(new Http403ForbiddenEntryPoint() { }) .and() .authenticationProvider(getProvider()) .formLogin() .loginProcessingUrl("/login") .successHandler(new AuthentificationLoginSuccessHandler()) .failureHandler(new SimpleUrlAuthenticationFailureHandler()) .and() .logout() .logoutUrl("/logout") .logoutSuccessHandler(new AuthentificationLogoutSuccessHandler()) .invalidateHttpSession(true) .and() .authorizeRequests() .antMatchers("/login").permitAll() .antMatchers("/logout").permitAll() .antMatchers("/user").authenticated() .anyRequest().permitAll(); } private class AuthentificationLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_OK); } } private class AuthentificationLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_OK); } } @Bean public AuthenticationProvider getProvider() { AppAuthProvider provider = new AppAuthProvider(); provider.setUserDetailsService(userDetailsService); return provider; } }
We define how users are managed in the configure(AuthenticationManagerBuilder auth) method. Here the user management is dynamic, we manage users via the service UserSevice.
Protected resources’ management is done on the level of the configure(HttpSecurityhttp) function. Any access to url / user / ** needs to be authenticated. The authentication is done via a request of type Post to url / login.
POSTMAN TESTS
We are going to test the access to our application using Postman Rest, which is a API web client.
By accessing GET: / user / we have an authentication error as below:
Access to GET: / user / needs to be authenticated beforehand.
To authenticate it we will access POST: / login with a User present in our database.
We have a 200 Status, so authentication has worked.
So we will again access GET: / user /
THE FINAL WORD
This article showed us how to use Spring Security to secure its Spring Boot application. You can find the complete code on GitHub.