1. Introduction
La plupart du temps, lors de la sécurisation d’une application Web Spring ou d’une API REST, les outils fournis par Spring Security sont largement suffisants, mais nous recherchons parfois un comportement plus spécifique.
Dans ce tutoriel, nous allons écrire un AccessDecisionVoter personnalisé et montrer comment il peut être utilisé pour extraire la logique d’autorisation d’une application Web et la séparer de la logique métier de l’application.
2. Scénario
Pour illustrer le fonctionnement de AccessDecisionVoter , nous allons implémenter un scénario avec deux types d’utilisateur USER et ADMIN, dans lequel un USER peut accéder au système uniquement pendant des minutes paires, tandis qu’un ADMIN sera toujours autorisé.
3. AccessDecisionVoter Implémentations
Dans un premier temps, nous allons décrire quelques-unes des implémentations fournies par Spring qui participeront avec notre électeur coutumier à la décision finale concernant l’autorisation. Nous verrons ensuite comment implémenter un votant personnalisé.
3.1. Les implémentations AccessDecisionVoter par défaut
Spring Security fournit plusieurs implémentations de AccessDecisionVoter .
Nous en utiliserons quelques uns dans le cadre de notre solution de sécurité ici.
Voyons comment et quand les implémentations par défaut des électeurs voteront.
Le AuthenticatedVoter émettra un vote basé sur le niveau d’authentification de l’objet Authentication - recherchant spécifiquement un prédécessal entièrement authentifié, un authentifié avec Remember-me ou, enfin, anonyme.
Le RoleVoter vote si l’un des attributs de configuration commence par la chaîne «ROLE » . Si c’est le cas, il recherchera le rôle dans la liste GrantedAuthority de l’objet Authentication__.
WebExpressionVoter nous permet d’utiliser SpEL (Spring Expression Language) pour autoriser les requêtes à l’aide de l’annotation @ PreAuthorize .
Par exemple, si nous utilisons Java config:
@Override
protected void configure(final HttpSecurity http) throws Exception {
...
.antMatchers("/").hasAnyAuthority("ROLE__USER")
...
}
Ou en utilisant une configuration XML - nous pouvons utiliser SpEL dans une balise intercept-url , dans la balise http :
<http use-expressions="true">
<intercept-url pattern="/"
access="hasAuthority('ROLE__USER')"/>
...
</http>
3.2. Implémentation personnalisée de AccessDecisionVoter
Créons maintenant un voteur personnalisé - en implémentant l’interface AccessDecisionVoter :
public class MinuteBasedVoter implements AccessDecisionVoter {
...
}
La première des trois méthodes que nous devons fournir est la méthode vote . La méthode vote est la partie la plus importante de l’électeur personnalisé et correspond à la logique d’autorisation.
La méthode vote peut renvoyer trois valeurs possibles:
-
ACCESS GRANTED__ - l’électeur donne une réponse affirmative
-
ACCESS DENIED__ - l’électeur donne une réponse négative
-
ACCESS ABSTAIN__ - l’électeur s’abstient de voter
Implémentons maintenant la méthode vote :
@Override
public int vote(
Authentication authentication, Object object, Collection collection) {
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.filter(r -> "ROLE__USER".equals(r)
&& LocalDateTime.now().getMinute() % 2 != 0)
.findAny()
.map(s -> ACCESS__DENIED)
.orElseGet(() -> ACCESS__ABSTAIN);
}
Dans notre méthode vote , nous vérifions si la requête provient d’un USER . Si tel est le cas, nous renverrons ACCESS GRANTED s’il s’agit d’une minute paire, sinon nous renverrons ACCESS DENIED. Si la demande ne provient pas d’un USER, nous nous abstenons du vote et nous retournons ACCESS ABSTAIN__.
La deuxième méthode indique si l’électeur prend en charge un attribut de configuration particulier. Dans notre exemple, l’électeur n’a besoin d’aucun attribut de configuration personnalisé. Nous renvoyons donc true :
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
La troisième méthode indique si l’électeur peut voter pour le type d’objet sécurisé ou non. Puisque notre votant n’est pas concerné par le type d’objet sécurisé, nous retournons true :
@Override
public boolean supports(Class clazz) {
return true;
}
4. Le AccessDecisionManager
La décision d’autorisation finale est gérée par le AccessDecisionManager .
Le AbstractAccessDecisionManager contient une liste de __AccessDecisionVoter __s - chargés de voter indépendamment les uns des autres.
Il existe trois implémentations pour le traitement des votes afin de couvrir les cas d’utilisation les plus courants:
-
AffirmativeBased - accorde l’accès si l’un des
_AccessDecisionVoter s renvoient un vote affirmatif ** ConsensusBased_ - accorde l’accès s’il y a plus de votes affirmatifs
que négatif (en ignorant les utilisateurs qui s’abstiennent) ** UnanimousBased__ - accorde l’accès si chaque électeur s’abstient ou
renvoie un vote affirmatif
Bien sûr, vous pouvez implémenter votre propre AccessDecisionManager avec votre logique de prise de décision personnalisée.
5. Configuration
Dans cette partie du didacticiel, nous examinerons les méthodes basées sur Java et XML pour configurer notre AccessDecisionVoter personnalisé avec un AccessDecisionManager .
5.1. Configuration Java
Créons une classe de configuration pour Spring Web Security:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}
Et définissons un bean AccessDecisionManager qui utilise un gestionnaire UnanimousBased avec notre liste personnalisée de votants:
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter(),
new MinuteBasedVoter());
return new UnanimousBased(decisionVoters);
}
Enfin, configurons Spring Security pour utiliser le bean défini précédemment comme valeur par défaut AccessDecisionManager :
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.anyRequest()
.authenticated()
.accessDecisionManager(accessDecisionManager());
}
5.2. Configuration XML
Si vous utilisez la configuration XML, vous devrez modifier votre fichier spring-security.xml (ou le fichier contenant vos paramètres de sécurité).
Tout d’abord, vous devrez modifier la balise <http> :
<http access-decision-manager-ref="accessDecisionManager">
<intercept-url
pattern="/** ** "
access="hasAnyRole('ROLE__ADMIN', 'ROLE__USER')"/>
...
</http>
Ensuite, ajoutez un haricot pour l’électeur personnalisé:
<beans:bean
id="minuteBasedVoter"
class="org.baeldung.voter.MinuteBasedVoter"/>
Ajoutez ensuite un bean pour le AccessDecisionManager :
<beans:bean
id="accessDecisionManager"
class="org.springframework.security.access.vote.UnanimousBased">
<beans:constructor-arg>
<beans:list>
<beans:bean class=
"org.springframework.security.web.access.expression.WebExpressionVoter"/>
<beans:bean class=
"org.springframework.security.access.vote.AuthenticatedVoter"/>
<beans:bean class=
"org.springframework.security.access.vote.RoleVoter"/>
<beans:bean class=
"org.baeldung.voter.MinuteBasedVoter"/>
</beans:list>
</beans:constructor-arg>
</beans:bean>
Voici un exemple de balise <authentication-manager> prenant en charge notre scénario:
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user" password="pass" authorities="ROLE__USER"/>
<user name="admin" password="pass" authorities="ROLE__ADMIN"/>
</user-service>
</authentication-provider>
</authentication-manager>
Si vous utilisez une combinaison de configurations Java et XML, vous pouvez importer le fichier XML dans une classe de configuration:
@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class XmlSecurityConfig {
public XmlSecurityConfig() {
super();
}
}
6. Conclusion
Dans ce didacticiel, nous avons étudié un moyen de personnaliser la sécurité d’une application Web Spring à l’aide de _AccessDecisionVoter s. Nous avons vu des électeurs fournis par Spring Security qui ont contribué à notre solution. Nous avons ensuite expliqué comment implémenter un AccessDecisionVoter_ personnalisé.
Ensuite, nous avons discuté de la façon dont le AccessDecisionManager prend la décision d’autorisation finale et nous avons montré comment utiliser les implémentations fournies par Spring pour prendre cette décision après que tous les électeurs aient voté.
Ensuite, nous avons configuré une liste de AccessDecisionVoters avec un AccessDecisionManager via Java et XML.
L’implémentation est disponible dans le projet Github
Lorsque le projet est exécuté localement, la page de connexion est accessible à l’adresse suivante:
http://localhost:8080/spring-security-custom-permissions/login
Les informations d’identification pour USER sont «utilisateur» et «pass» et les informations d’identification pour ADMIN sont «admin» et «pass».