Spring Sécurity est un framework permettant d'ajouter aux applications Spring authentification et contrôle d'accès.
Spring security ajoute au Dispatcher servlet de Spring MVC un ensemble de filtres (servlet filters) qualifié de filter chain.
Ajouter la dépendance suivante à pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
L'intégration de Spring security met en place une authentification basique par défaut, avec un utilisateur de non user, ayant le rôle USER,
dont le password s'affiche dans la console de démarrage de Spring, utilisable en mode développement :
Il est possible de modifier l'utilisateur par défaut dans application.properties :
spring.security.user.name=boris spring.security.user.password=boris-password spring.security.user.roles=manager
La configuration se fait au travers d'une classe de configuration activant la sécurité :
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(
(req) -> req.requestMatchers(
PathPatternRequestMatcher.withDefaults().matcher("/"),
PathPatternRequestMatcher.withDefaults().matcher("/css/**"),
PathPatternRequestMatcher.withDefaults().matcher("/js/**"),
PathPatternRequestMatcher.withDefaults().matcher("/img/**"),
PathPatternRequestMatcher.withDefaults().matcher("/register/**")
)
.permitAll()
.anyRequest()
.authenticated()
);
return http.build();
}
}
Créer ou reprendre votre entity User pour gérer les utilisateurs et leur rôle :
Elle devra implémenter l'interface UserDetails :
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(length = 50, unique = true)
private String login;
@Column(length = 255)
private String password;
@Column(length = 255)
private String email;
@ManyToOne
private Role role;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
if (role != null) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return login;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Créer le repository associé et ajouter une méthode permettant de rechercher un User par son username ou son email :
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByLoginOrEmail(String login, String email);
}
Créer un Service implémentant UserDetailsService :
Ce service retourne depuis la base de données un utilisateur recherché par son nom ou son email et retourne une instance de UserDetails :
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserRepository uRepo;
@Autowired
private RoleRepository rRepo;
@Autowired
private PasswordEncoder pEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> optUser = uRepo.findByLoginOrEmail(username, username);
return optUser.orElseThrow(() -> new UsernameNotFoundException("Utilisateur inconnu"));
}
public void encodePassword(User user) {
user.setPassword(pEncoder.encode(user.getPassword()));
}
}
Modifier la configuration de spring security pour définir le hashage du mot de passe (Bcrypt) :
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
...
return http.build();
}
@Primary
@Bean
public UserDetailsService getUserDetailsService() {
return new UserService();
}
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
Le Bean UserDetailsService est marqué comme @Primary pour être le premier choisi par Spring lors de l'injection.
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
...
@Bean
public DaoAuthenticationProvider authenticationProvider(UserDetailsService userService) {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider(userService);
auth.setPasswordEncoder(getPasswordEncoder());
return auth;
}
}
Ne pas oublier de hasher le password User avant création :
@Service
public class UserService implements UserDetailsService {
...
public User createUser(String login, String password) {
User user = new User();
user.setLogin(login);
user.setPassword(password);
encodePassword(user);
user.setRole(getRole("ROLE_USER"));
return uRepo.save(user);
}
}
Récupération de l'utilisateur connecté en tant que ModelAttribute pour utilisation dans les vues :
@ControllerAdvice
public class MainAdvice {
@ModelAttribute("activeUser")
public User activeUser(Authentication auth) {
return (auth == null) ? null : (User) auth.getPrincipal();
}
}
Créer un template pour le formulaire de connexion :
<form method="post" action="./login">
<input type="text" name="username" placeholder="E-mail address or username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Login</button>
</form>
Ajouter une route pour le login
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("/forms/loginForm");
}
}
Modifier la configuration dans WebSecurityConfig :
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.
...
.formLogin(
(form) -> form.loginPage("/login").defaultSuccessUrl("/", true).permitAll()
);
return http.build();
}
Pour autoriser l'accès à la console h2 :
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests( // (1)
(req) -> req.requestMatchers(
PathRequest.toH2Console(), // (2)
...
)
.permitAll()
.anyRequest()
.authenticated()
).headers(
(headers) -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) // (3)
);
return http.build();
}
}
Spring Security distingue les authorities, accessible par une simple chaîne (encapsulée ensuite dans un objet de type GrantedAuthority) : USER, ADMIN…
et le rôle équivalent, authority préfixée par ROLE_ : ROLE_USER, ROLE_ADMIN….
Il est possible de faire référence à l'un ou à l'autre indiféremment.
Le rôle des utilisateurs pourra dons être stocké en base de données, sous la forme d'une chaîne, ou d'une liste de chaînes.
Il est possible de définir les authorities de manière hiérarchique :
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_REDACTOR\n ROLE_REDACTOR > ROLE_USER");
return hierarchy;
}
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
retur