Définition de Spring Security

Spring Security est un Framework de sécurité léger qui fournit une authentification et un support d’autorisation afin de sécuriser les applications Spring. Il est livré avec des implémentations d’algorithmes de sécurité populaires.

Cet article nous guide tout au long du processus de création d’un simple exemple de connexion à une  application avec Spring Boot, Spring Security, Spring Data JPA et MYSQL

 Configuration d’application

Commençons par une application très basique (en termes d’installation nécessaire) qui démarre un contexte d’application Spring. Deux outils qui nous aideront avec cela sont Maven et Spring Boot. Je sauterai les lignes qui ne sont pas particulièrement intéressantes comme la configuration du référentiel Maven. Vous pouvez trouver le code complet sur GitHub.

Pour commencer nous allons ajouter la dépendance suivante au pom de notre application.

<dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>5.0.0.RC1</version>
</dependency>

Au démarrage de notre application nous avons dans la console les informations suivantes :

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.8.RELEASE)

Using default security password: 042fb27f-d44a-49eb-92a8-15f246421629

Spring Boot nous fourni donc un mot de passe par défaut.

Nous venons  d’ajouter Spring Security au classpath de notre application, Spring instaure une configuration par défaut, désormais pour accéder à notre application nous aurons besoin d’un User et d’un mot de passe.

Pour accéder donc à notre application avec la configuration par défaut de Spring, nous entrons l’user comme nom d’utilisateur user et le mot de passe par défaut fourni par Spring , celui affiché dans la console au démarrage de notre application(ici 042fb27f-d44a-49eb-92a8-15f246421629) dans le formulaire d’authentification.

Dans la suite de cette article , nous allons personnaliser cette configuration.

Création de l’entité User

Nous allons créer la classe User.java,  cette classe implémente interface UserDetails.

L’interface fournit des informations sur l’utilisateur principal. Les implémentations ne sont pas utilisées directement par Spring Security à des fins de sécurité, ils stockent simplement les informations utilisateur qui sont ensuite encapsulées dans des objets d’authentification, Cela permet de stocker des informations non liées à la sécurité (telles que les adresses e-mail etc.) dans un emplacement approprié.

@Data
@Entity
@Table(name = "User")
@EqualsAndHashCode(of = "userId")

public class User implements Serializable, UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer userId;

    @Column
    private String name;

    @Column
    private String password;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return name;
    }

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

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

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

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

Ci-dessous les scripts de création et d’alimentation de notre table dans notre base de données Mysql.

CREATE TABLE `user` (
  `password` varchar(32) NOT NULL,
  `userId` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `user_id` int(11) NOT NULL,
  PRIMARY KEY (`userId`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
GO


INSERT INTO `user`
(`password`,
`name`)
VALUES
('test',
'test');
GO

Implémentation de La classe AppAuthenticationProvider

Spring Security fournit une variété d’options pour effectuer l’authentification, toutes ces options suivent un contrat simple. Une demande d’authentification est traitée par un AuthenticationProvider et un objet entièrement authentifié avec des informations d’identification complètes est renvoyé.

L’implémentation standard et la plus courante est le DaoAuthenticationProvider – qui récupère les détails de l’utilisateur à partir d’un simple DAO utilisateur en lecture seule – le UserDetailsService. Ce service de détails de l’utilisateur a uniquement accès au nom d’utilisateur afin de récupérer l’entité utilisateur complète et dans un grand nombre de scénarios, cela suffit.

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;

    }
}

Implémentation du Repository UserRepository

Nous allons créer la classe UserRepository.java, cette classe hérite de la classe JpaRepository, la fonction findUserWithName(String name) permet de récupérer un User à partir de son nom d’utilisateur.

public interface UserRepository extends JpaRepository<User, Integer> {

    @Query(" select u from User u " +
            " where u.name = ?1")
    Optional<User> findUserWithName(String name);
}

Implémentation de La classe AppAuthenticationProvider

Spring Security fournit une variété d’options pour effectuer l’authentification, toutes ces options suivent un contrat simple. Une demande d’authentification est traitée par un AuthenticationProvider et un objet entièrement authentifié avec des informations d’identification complètes est renvoyé.

L’implémentation standard et la plus courante est le DaoAuthenticationProvider – qui récupère les détails de l’utilisateur à partir d’un simple DAO utilisateur en lecture seule – le UserDetailsService. Ce service de détails de l’utilisateur a uniquement accès au nom d’utilisateur afin de récupérer l’entité utilisateur complète et dans un grand nombre de scénarios, cela suffit.

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;

    }
}

Implémentation du Service UserService

Nous allons créer la classe UserService.java ,cette classe implémente interface UserDetailsService.
L’interface UserDetailsService est utilisée pour récupérer les données liées à l’utilisateur. Il a une méthode nommée loadUserByUsername qui trouve une entité utilisateur basée sur le nom d’utilisateur et peut être substituée pour personnaliser le processus de recherche de l’utilisateur. Il est utilisé par DaoAuthenticationProvider pour charger des détails sur l’utilisateur lors de l’authentification.

@Service
@Slf4j

public class UserService implements UserDetailsService {

    private final UserRepository userRepository;

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

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Objects.requireNonNull(email);
        return userRepository.findUserWithName(email)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }

}

Implémentation de la classe de configuration SecurityConfig

Pour personnaliser la configuration nous allons créer une classe qui hérite de WebSecurityConfigurerAdaptater, cette classe doit avoir les annotations @EnableWebSecurity et @Configuration, les classes de configuration sont scannées au démarrage de application.

@Configuration
@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserService userDetailsService;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    //@Autowired
    //private RememberMeServices rememberMeServices;

    @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 Http401AuthenticationEntryPoint("App header"))
                .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;

    }
}

Nous définissons la manière donc sont gérés les utilisateurs dans la méthode configure(AuthenticationManagerBuilder auth). Ici la gestion des utilisateurs est dynamique, on gère les utilisateurs via le service UserSevice.

La gestion des ressources à protéger se fait au niveau de la fonction configure(HttpSecurity http), tout accès à url <host>/user/** nécessite d’être authentifié, authentification se fait via une requête de type Post à url <host>/login.

Tests Postman

Nous allons tester accès à notre application en utilisant Postman Rest qui est un client pour API web.

En accédant à GET : <host>/user/ nous avons une erreur d’authentification comme ci-dessous :

 

Accès à GET : <host>/user/ nécessite d’être authentifier au préalable.

Pour nous authentifier nous allons accéder à POST : <host>/login avec un User présent dans notre base de données

Nous avons un Statut 200, authentification a donc fonctionnée.

Nous allons donc à nouveau accéder à GET : <host>/user/

 

Le mot de la fin

Cet article nous a montré comment utiliser Spring Security pour sécuriser son application Spring Boot. Vous pouvez trouver le code complet sur GitHub