
Comment s’authentifier avec Spring security
Ce tutoriel vous montrera comment mettre en œuvre un processus de connexion à l’aide de la pile technologique suivante:
- Spring Boot
- Spring Security
- JPA (Hibernate)
- Spring Data JPA
- PostgreSQL
- Spring MVC
- Thymeleaf
- Bootstrap
- Java 8
C’est quoi Spring Security?
Spring Security est l’un des modules de sécurité de Spring Framework.
Il s’agit d’une infrastructure de sécurité Java SE / Java EE qui fournit une authentification, une autorisation, une authentification unique et d’autres fonctionnalités de sécurité pour les applications Web ou les applications d’entreprise. On peut aussi s’authentifier avec Spring Security en utilisant un Token ou le Framework Ionic
Définir les entités JPA
L’entité JPA est définie avec l’annotation @Entity et représente une table dans la base de données.
@Table mappe l’entité avec la table. Si aucune @Table n’est définie, la valeur par défaut est utilisée pour le nom de classe de l’entité.
@Id déclare la propriété d’identifiant de l’entité (clé primaire).
@JoinColumn indique que l’entité est le propriétaire de la relation: la table correspondante contient une colonne avec une clé étrangère pour la table référencée.
mappedBy indique que l’entité est l’inverse de la relation.
src / principal / java / com / intellitech / springlabs / model / User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
package com.intellitech.springlabs.model; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; @Entity @Table(name = "user", schema = "public") public class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @Column(name = "first_name", length = 75) private String firstName; @Column(name = "last_name", length = 80) private String lastName; @Column(name = "username", length = 65) private String username; @Column(name = "password", length = 64) private String password; @Column(name = "email", unique = true, length = 115) private String email; @OneToOne(fetch = FetchType.EAGER) @JoinColumn(name = "role_id") private Role role; public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } |
src/main/java/com/intellitech/springlabs/model/Role.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.intellitech.springlabs.model; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "role", schema = "public") public class Role implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Integer id; @Column(name = "role_name", length = 65) private String roleName; public Role() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } } |
Spring Dépôts de données JPA
Spring Data JPA contient des référentiels intégrés qui implémentent des fonctions communes d’utilisation de la base de données: findOne, findAll, save,…
src / main / java / com / intellitech / repository / UserRepository.java
1 2 3 4 5 6 7 8 9 10 |
package com.intellitech.springlabs.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.intellitech.springlabs.model.User; public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.username=:usernameOrEmail OR u.email=:usernameOrEmail") User findByUsernameOrEmail(String usernameOrEmail); } |
src/main/java/com/intellitech/repository/Role.java
1 2 3 4 5 6 7 8 9 |
package com.intellitech.springlabs.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.intellitech.springlabs.model.Role; public interface RoleRepository extends JpaRepository{ } |
Couche de service
Créons maintenant la couche de service utilisateur (interface et implémentation). Nous allons injecter le UserRepository dans notre classe de service.
src/main/java/com/intellitech/service/UserService.java
1 2 3 4 5 6 7 |
package com.intellitech.springlabs.service; import com.intellitech.springlabs.model.User; public interface UserService { User findByUsernameOrEmail(String usernameOrEmail); } |
src/main/java/com/intellitech/service/impl/UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package com.intellitech.springlabs.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.intellitech.springlabs.model.User; import com.intellitech.springlabs.repository.UserRepository; import com.intellitech.springlabs.service.UserService; @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override @Transactional(readOnly = true) public User findByUsernameOrEmail(String usernameOrEmail) { User user = null; try { user = userRepository.findByUsernameOrEmail(usernameOrEmail); } catch (Exception e) { throw e; } return user; } } |
Sécuriser l’application
Pour implémenter la connexion / authentification avec Spring Security, on doit implémenter l’interface org.springframework.security.core.userdetails.UserDetailsService. Cette interface nécessite une seule méthode loadUserByUsername (nom d’utilisateur String).
src/main/java/com/intellitech/security/CustomUserDetailsService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package com.intellitech.springlabs.security; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.intellitech.springlabs.model.Role; import com.intellitech.springlabs.model.User; import com.intellitech.springlabs.service.UserService; @Service("customUserDetailsService") public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserService userService; @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username.trim().isEmpty()) { throw new UsernameNotFoundException("username is empty"); } User user = userService.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User " + username + " not found"); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getGrantedAuthorities(user)); } private List<GrantedAuthority> getGrantedAuthorities(User user) { List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); Role role = user.getRole(); authorities.add(new SimpleGrantedAuthority(role.getRoleName())); return authorities; } } |
Pour les applications Web, on doit créer une classe spécifique qui étend WebSecurityConfigurerAdapter.
Cette classe propose plusieurs méthodes pour configurer notre configuration de sécurité.
protected void configure (HttpSecurity http): cette méthode incorporera toutes les demandes autorisées et les URL permis à l’accès public dans l’application et celles qui sont sécurisées. Dans notre cas, nous autorisons l’accès à tous uniquement pour la page d’accueil. Toute autre demande des utilisateurs doit être sur l’authentification avant l’accès. Nous avons également la possibilité de définir une page de connexion pour les utilisateurs authentifiés.
protected void configure (AuthenticationManagerBuilder authManagerBuilder): cette méthode de substitution permet de spécifier à l’AuthentificationManagerBuilder les types UserDetailsService et PasswordEncoder que nous allons utiliser.
src/main/java/com/intellitech/SecurityConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package com.intellitech.springlabs; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("customUserDetailsService") private UserDetailsService customUserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().antMatchers("/").permitAll() .anyRequest().authenticated() .and().formLogin().loginPage("/login").defaultSuccessUrl("/welcome").failureUrl("/login?error=true").permitAll() .and().logout().deleteCookies("JSESSIONID").logoutUrl("/logout").logoutSuccessUrl("/login"); } @Override protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception { authManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(bCryptPasswordEncoder()); } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } |
src/main/java/com/intellitech/SpringlabsApplication.java
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.intellitech.springlabs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringlabsApplication { public static void main(String[] args) { SpringApplication.run(SpringlabsApplication.class, args); } } |
Gérer les vues
La demande de registre est utilisée pour mapper toutes les vues de l’application.
il mappe les deux vues:
- page login.html
- html page
src/main/java/com/intellitech/WebMvcConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.intellitech.springlabs; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter{ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.addViewController("/welcome").setViewName("welcome"); registry.addViewController("/").setViewName("login"); } } |
Voici la source de code thymeleaf pour les deux vues décrites ci-dessus. La vue basée sur Thymeleaf doit être située dans le dossier resources / templates:
src/main/java/ressources/templates/login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title>Login</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <br/><br/> <form th:action="@{/login}" method="POST" class="form-signin" style="width:30%;margin:auto"> <h3 class="form-signin-heading">LOGIN PAGE</h3> <br/> <div align="center" th:if="${param.error}"> <p style="font-size: 20; color: #FF1C19;">Username or password is invalid</p> </div> <input type="text" id="username" name="username" th:placeholder="Username or email" class="form-control" /> <br/> <input type="password" th:placeholder="Password" id="password" name="password" class="form-control" /> <br /> <button class="btn btn-lg btn-primary btn-block" name="Submit" value="Login" type="Submit" th:text="Login"></button> </form> </div> </body> </html> |
src/main/java/resources/templates/welcome.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Welcome</title> </head> <body> <h1 style="width: 50%; margin: auto">WELCOME TO SPRING LABS 2017</h1> <br/> <a th:href="@{/logout}"> <button type="button">LOGOUT</button> </a> </body> </html> |
Lancer l’application
Vous devez d’abord créer une base de données avec le nom springlabs et exécuter l’application en lançant la classe SpringBootApplication à l’aide de la commande mvn spring-boot: run et visiter localhost: 8080
Exécutez le script dans la base de données PostgreSQL:
1 2 3 4 |
INSERT INTO public.role (id, role_name) VALUES (1, 'ROLE_ADMIN'); INSERT INTO public.role (id, role_name) VALUES (2, 'ROLE_USER'); INSERT INTO public.user (id, first_name, last_name, email, password, username, role_id) VALUES (1, 'Admin', 'Admin','admin@gmail.com', '$2a$10$bpNMKeaQXKpJ4JVxOHWvu.tZdmCLT9nKcZreJ/ELfCgmTCyhC7GPy', 'admin', 1); INSERT INTO public.user (id, first_name, last_name, email, password, username, role_id) VALUES (2, 'User', 'User','user@gmail.com', '$2a$10$TA.UfUqLa8uDeGkt95FfLeq7T5Y5vpDpzAtvJrHSLzLliY/PARXUq', 'user', 2); |
Authentifiez-vous avec l’un des deux utilisateurs:
: username=admin, password=admin
: username=user, password=user
Télécharger le code source à partir de