Se substituer à un autre utilisateur grâce au SwitchUserFilter de Spring Security

Un cas d’utilisation, assez fréquemment demandé, est de permettre à un super utilisateur (admin ou super admin) de se substituer à un simple utilisateur ayant moins de privilèges, et ce sans avoir son mot de passe.

Une fois que le Super User est déjà authentifié (avec son propre login et mot de passe), la fonctionnalité Switch User lui permet de se connecter en tant qu’un autre utilisateur, en saisissant le login de l’utilisateur auquel il veut se substituer. Dans la session de cet utilisateur, le Super User aura les mêmes rôles et autorisations que ce dernier.

Il peut se déconnecter de la session de l’utilisateur « imité » et revenir à sa propre session.

Le système sait par contre quel utilisateur qui réellement connecté, en cas d’audit ou d’action malveillante.

Dépendances maven du projet :

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity4</artifactId>
		</dependency>
pom.xml

 

L’URL cible de la page de changement d’utilisateur (dans mon menu) :

<li sec:authorize="hasRole('ROLE_ADMIN')"><a  th:href="@{/admin/switchUSer}">Switch User </a></li>
menu.html

Le contrôleur : AdminController

@RequestMapping("/admin")
@Controller
public class AdminCotroller {
	
@GetMapping("/switchUser")
	public String switchUser(Model model) {

		return "admin/switchUser/login";
	}
}
AdminCotroller.java

Ainsi un clic sur le lien ci-dessous (/admin/switchUser) affiche la page login.html qui est dans le dossier "src/main/resources/admin/switchUser "

Le formulaire login.html :

Il a un seul champ de saisie du "username" et un bouton pour le soumettre.

<form th:action="@{/admin/switchUser/login}" method="GET" class="form-signin">
				
	<h3 class="form-signin-heading" >Changer d'utilisateur</h3>	<br/>

	<input type="text" id="username" name="username"  th:placeholder="Identifiant" class="form-control" /> <br/> 

	<input class="btn btn-lg btn-primary btn-block" type="submit" value="Login" />
</form>
login.html

Remarque : On n’a pas besoin de soumettre le formulaire en « POST ».

L’URL soumis sera de la forme : http://localhost:8080/admin/switchUser/login?username=bob

Le SwitchUserFilter:

C’est ce filtre qui crée l’URL qui permet de modifier le contexte de sécurité pour connecter un autre utilisateur.

	@Bean
	public SwitchUserFilter switchUserFilter() {

		SwitchUserFilter filter = new SwitchUserFilter();
		filter.setUserDetailsService(userDetailsService);
		filter.setSwitchUserUrl("/admin/switchUser/login");
		filter.setTargetUrl("/");
		filter.setSwitchFailureUrl("/not-authorized");
		filter.setExitUserUrl("/logout/switchUser");
		return filter;
	}
SecurityConfiguration.java

Les paramètres à passer au bean SwitchUserFilter (liste non exhaustive) :

  • userDetailsService : une référence du bean userDetailsService configuré par Spring
  • switchUserUrl : l’URL du Switch User : toutes les requêtes vers /admin/switchUser/login seront traitées par le SwitchUserFilter.
  • targetUrl : l’URL de redirection si tout se passe bien.
  • switchFailureUrl : l’URL de redirection (ici une page d’erreur) en cas d’échec.
  • exitUserUrl : l’URL de déconnexion une fois qu’on a switché d’utilisateur. Quand on appelle cette URL, on se déconnecte en tant qu’utilisateur « switché » pour revenir à notre session.

Remarque : une fois qu'on a switché de user, on a un rôle supplémentaire "ROLE_PREVIOUS_ADMINISTRATOR". Ceci correspond à une instance de la classe "SwitchUserGrantedAuthority". Le vrai utilisateur (Admin) peut être extrait par la méthode : SwitchUserGrantedAuthority.getSource().

Ainsi on peut vérifier si le user a ce rôle avant d'appeler l'URL de déconnexion (exitUserUrl ) de la session "usurpée". Dans ce cas on revient à notre propre session

<span sec:authorize="hasRole('ROLE_PREVIOUS_ADMINISTRATOR')">   <a th:href="@{/logout/switchUser}">Déconnexion</a></span>	
header.html

Le bean UserDetailsService

@Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// implémentation de votre logique de chargement de l'utilisateur
	}	
}
UserDetailsServiceImpl.java

La méthode "loadUserByUsername" est appelée pour charger l'instance du UserDetailsService de l'utilisateur.

 

Et pour finir, définir la configuration Spring Security pour ajouter le filtre SwitchUser et les contrôles d'accès

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http.csrf().disable()
			
		// some confs
			.addFilterAfter(switchUserFilter(), FilterSecurityInterceptor.class)
		// other confs
			.and()
            	.logout()
            	.logoutUrl("/logout")           	
                .antMatchers("/logout/switchUser").authenticated()
                .antMatchers("/admin/switchUser/**").hasAuthority("ROLE_ADMIN")
              // etc.
		// @formatter:on
	}
SecurityConfiguration.java

 

Voila, j'espère que je n'ai rien oublié !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.