J'ai 2 applications Spring Boot distinctes, l'une servant de serveur d'autorisation OAuth 2 et l'autre de serveur de ressources. J'utilise la variable RemoteTokenServices
de Spring dans mon serveur de ressources pour vérifier les jetons du serveur d'autorisation. À présent, j'essaie de définir un code de contrôleur protégé dans mon application de serveur de ressources, mais je ne sais pas comment mapper la classe UserDetails
sur le principal d'authentification fourni via le mécanisme OAuth 2.
J'ai configuré mon serveur d'autorisation avec une variable TokenEnhancer
personnalisée qui ajoute plus de détails au jeton, de telle sorte que /oauth/check_token?token=<token>
renvoie des champs personnalisés que je souhaite mapper sur les contrôleurs de mon serveur de ressources.
Dans une configuration plus monolithique où le serveur d'autorisation est également le serveur de ressources, je peux définir les méthodes du contrôleur qui utilisent le principal authentifié de la manière suivante:
//User implements UserDetails
public Map<String, Object> getResource(@AuthenticationPrincipal User user) {
//code that uses the user object
}
Cependant, cela ne semble pas fonctionner aussi facilement dans une approche plus distribuée. Le mappage échoue et le paramètre user
finit par être un objet null. J'ai essayé d'utiliser l'approche suivante:
public Map<String, Object> getResource(Authentication authentication) {
//code that uses the authentication object
}
Bien que le code ci-dessus mappe correctement les informations d'authentification, il ne me permet pas d'accéder directement aux champs personnalisés que j'ai définis via la variable TokenEnhancer
dont j'ai parlé plus tôt. Je n'arrive pas à trouver quoi que ce soit dans la documentation du printemps à ce sujet.
Pour résoudre le problème, permettez-moi d'abord de passer en revue un peu d'arrière-plan architectural. L'objet UserDetails
automatiquement mappé via le @AuthenticationPrincipal
provient du champ principal
de l'objet Authentication
actif. Un contrôleur de serveur de ressources a accès à un objet OAuth2Authencation
, qui est une instance spécialisée de Authentication
pour la structure de sécurité Spring OAuth2, simplement en la déclarant dans les paramètres de méthode.
public void controllerMethod(OAuth2Authentication authentication) {
//controller definition
}
Sachant cela, le problème consiste maintenant à savoir comment s'assurer que la méthode getPrincipal()
dans l'objet Authentication
est une instance de ma classe UserDetails
personnalisée. La RemoteTokenServices
que j'utilise dans l'application du serveur de ressources utilise une instance de AccessTokenConverter
pour interpréter les détails des jetons envoyés par le serveur d'autorisation. Par défaut, il utilise DefaultAccessTokenConverter
, qui définit simplement le principal d'authentification en tant que nom d'utilisateur, qui est un String
. Ce convertisseur utilise UserAuthenticationConverter
pour convertir les données provenant du serveur d'autorisation en une instance de Authentication
. Voici ce dont j'avais besoin pour personnaliser:
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
tokenConverter.setUserTokenConverter(new DefaultUserAuthenticationConverter() {
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
Authentication authentication = super.extractAuthentication(map);
// User is my custom UserDetails class
User user = new User();
user.setSpecialKey(map.get("specialKey").toString());
return new UsernamePasswordAuthenticationToken(user,
authentication.getCredentials(), authentication.getAuthorities());
}
});
tokenServices.setAccessTokenConverter(tokenConverter);
Avec toutes ces configurations, le mécanisme @AuthenticationPrincipal
fonctionne maintenant comme prévu.
Avez-vous activé la AuthenticationPrincipalArgumentResolver
, comme après le xml?
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
Et vous devez implémenter la UserDetails
pour renvoyer votre propre CustomerUser object
. Vous pouvez ensuite utiliser l'annotation pour obtenir le principal directement.