La prise en charge d’OAuth2 de Spring Cloud Gateway est une partie importante du processus de sécurité des microservices. La principale raison d’utiliser un modèle de passerelle API est, bien sûr, de masquer les services du client externe. Cependant, lorsque nous avons décidé de cacher nos services, nous ne les avons pas sécurisés. Dans cet article, je vais vous montrer comment configurer Spring Cloud Gateway OAuth2 avec Spring Security et Keycloak.

Spring Cloud Gateway est un produit très utile. Vous pouvez profiter des nombreuses fonctionnalités intéressantes qu’il offre. L’un d’eux est le plafond des taux. Vous pouvez en savoir plus à ce sujet dans l’article Limitation de débit dans Spring Cloud Gateway avec Redis. Il vaut également la peine de se renseigner sur un disjoncteur et la tolérance aux pannes. Vous pouvez trouver des informations intéressantes à ce sujet dans les articles Interruption dans Spring Cloud Gateway avec Resilience4j et Délais d’expiration et tentatives dans Spring Cloud Gateway.

Code source

Si vous voulez l’essayer par vous-même, vous pouvez toujours consulter mon code source. Pour ce faire, vous devez cloner mon référentiel Microservices de sécurité du stylet-sonde. Alors tu devrais aller à gateway Annuaire et suivez mes instructions. 🙂 Si vous êtes intéressé par plus de détails sur Spring Security, vous devriez le lire Documentation.

Activer OAuth2 dans Spring Cloud Gateway

Afin d’activer la prise en charge d’OAuth2 pour l’application Spring Cloud Gateway, certaines dépendances doivent être ajoutées. Bien sûr que c’est spring-cloud-starter-gateway Une dépendance est requise pour activer la fonction de passerelle. Nous devons également impliquer spring-boot-starter-oauth2-client Activez la prise en charge du client Spring Security pour le framework d’autorisation OAuth 2.0 et OpenID Connect Core 1.0. Enfin, nous devons ajouter spring-cloud-starter-security pour activer le TokenRelay Filtre.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

Dans l’étape suivante, nous devons fournir les paramètres de configuration pour le client OAuth2. Puisque nous intégrons dans Keycloak, nous devons définir le nom de l’ID d’enregistrement (spring.security.oauth2.client.provider.[registrationId]) à keycloak. Ensuite, nous devons définir les Uris de token, authorization et userinfo Points de terminaison. D’autre part, nous pouvons définir une valeur pour un seul issuer Point final. La dernière propriété importante de cette section est user-name-attribute. Keycloak renvoie la connexion de l’utilisateur dans le preferred_username Attribut.

Nous définirons deux clients différents pour l’autorisation. Le premier d’entre eux spring-cloud-gateway contient la portée autorisée par notre méthode de test, tandis que la seconde spring-cloud-gateway-2 Pas.

spring:
  security:
    oauth2:
      client:
        provider:
          keycloak:
            token-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/token
            authorization-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/auth
            userinfo-uri: http://localhost:8080/auth/realms/master/protocol/openid-connect/userinfo
            user-name-attribute: preferred_username
        registration:
          keycloak-with-test-scope:
            provider: keycloak
            client-id: spring-with-test-scope
            client-secret: c6480137-1526-4c3e-aed3-295aabcb7609
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"
          keycloak-without-test-scope:
            provider: keycloak
            client-id: spring-without-test-scope
            client-secret: f6fc369d-49ce-4132-8282-5b5d413eba23
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"

Dans la dernière étape, nous devons configurer Spring Security. Puisque Spring Cloud Gateway est basé sur Spring WebFlux, nous devons annoter le bean de configuration @EnableWebFluxSecurity. Dans le springSecurityFilterChain Méthode nous activerons l’autorisation pour tous les échanges. Nous définirons également OAuth2 comme méthode de connexion par défaut et désactiverons éventuellement CSRF.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

   @Bean
   public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
      http.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
         .oauth2Login(withDefaults());
      http.csrf().disable();
      return http.build();
   }

}

Exécutez Keycloak et configurez-le

Nous exécutons Keycloak sur un conteneur Docker. Par défaut, Keycloak rend l’API et une console Web disponibles sur le port 8080. Cependant, ce numéro de port doit être différent du port de l’application Spring Cloud Gateway, nous allons donc le remplacer par 8888. Nous devons également définir un nom d’utilisateur et un mot de passe pour la console d’administration.

$ docker run -d --name keycloak -p 8888:8080 
   -e KEYCLOAK_USER=spring 
   -e KEYCLOAK_PASSWORD=spring123 
   jboss/keycloak

Ensuite, nous devons créer deux clients avec le même nom que celui défini dans la configuration de la passerelle. Les deux doivent avoir confidential Un URI de redirection valide est spécifié dans la section « Type d’accès ». Nous pouvons utiliser un simple caractère générique lors de la définition de l’adresse de redirection comme indiqué ci-dessous.

Le consommateur spring-with-test-scope aura la portée TEST attribué. En revanche, le deuxième client spring-without-test-scope n’aura pas la portée TEST attribué.

spring-cloud-gateway-oauth2-clientscope

Activer le recours OAuth2 dans Spring Cloud Gateway

Nous pouvons maintenant procéder à la mise en œuvre de l’application en aval. Pour l’exécuter, vous devez changer callme Répertoire dans le code source. Tout d’abord, nous devons inclure certaines dépendances Maven. le spring-boot-starter-web Starter fournit une assistance Web pour l’application Spring Boot. Avec spring-boot-starter-security Nous activons Spring Security pour notre microservice. le spring-security-oauth2-resource-server inclut la prise en charge de Spring Security pour les serveurs de ressources OAuth 2.0. Il est également utilisé pour protéger les API via des jetons de support OAuth 2.0. Enfin, que spring-security-oauth2-jose Le module contient le support Spring Security pour le framework JOSE (Javascript Object Signing and Encryption). Le framework JOSE offre une méthode de transfert sécurisé des créances entre les parties. Il prend en charge JWT et JWS (JSON Web Signature).

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-oauth2-resource-server</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-oauth2-jose</artifactId>
   </dependency>
</dependencies>

Dans l’étape suivante, nous devons configurer une connexion au serveur d’autorisation. Un serveur de ressources utilise la propriété spring.security.oauth2.resourceserver.jwt.issuer-uri pour déterminer les clés publiques du serveur d’autorisation et pour valider les jetons JWT entrants.

spring:
  application:
    name: callme
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/auth/realms/master

Nous devrions également déployer une configuration Spring Security. Nous devons d’abord annoter cela Configuration Haricot avec @EnableWebSecurity. Ensuite, nous devrions activer la sécurité basée sur les annotations pour les méthodes du contrôleur. Il permet un accès facile basé sur les rôles avec @PreAuthorize et @PostAuthorize. Pour activer une fonctionnalité de sécurité de méthode, nous devons utiliser des annotations @EnableGlobalMethodSecurity. Enfin, tout ce que nous devons faire est de configurer Spring Security pour autoriser toutes les demandes entrantes et valider les jetons JWT.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
   }
}

Enfin, jetons un coup d’œil à l’implémentation de la classe de contrôleur REST. C’est un single ping Méthode. Cette méthode n’est accessible que par le client avec le TEST Portée. Il y a une liste des zones attribuées à l’arrière Authentication Haricot.

@RestController
@RequestMapping("/callme")
public class CallmeController {

   @PreAuthorize("hasAuthority('SCOPE_TEST')")
   @GetMapping("/ping")
   public String ping() {
      SecurityContext context = SecurityContextHolder.getContext();
      Authentication authentication = context.getAuthentication();
      return "Scopes: " + authentication.getAuthorities();
   }
}

Configurer le routage sur Spring Cloud Gateway

La dernière étape avant de procéder aux tests consiste à configurer le routage dans l’application Spring Cloud Gateway. Depuis le service en aval (callme) fonctionne sur le port 8040 nous devons arrêter ça uri à http://127.0.0.1:8040. Pour transférer le jeton d’accès au callme-service Nous devons activer un filtre global TokenRelay. Pour nous assurer que tout fonctionne comme prévu, nous allons supprimer ceci Cookie avec l’ID de session. L’ID de session est généré sur la passerelle après l’exécution OAuth2Login.

spring:
  application:
    name: gateway
  cloud:
    gateway:
      default-filters:
        - TokenRelay
      routes:
        - id: callme-service
          uri: http://127.0.0.1:8040
          predicates:
            - Path=/callme/**
          filters:
            - RemoveRequestHeader=Cookie

Enfin, regardons la classe de passerelle principale. J’y ai ajouté deux points de terminaison utiles. D’abord d’eux GET / renvoie l’ID de session HTTP. Le deuxième d’entre eux GET /token renvoie le jeton d’accès JWT actuel. Après vous être connecté avec succès à Spring Cloud Gateway OAuth2, le résultat de sera affiché index Méthode.

@SpringBootApplication
@RestController
public class GatewayApplication {

   private static final Logger LOGGER = LoggerFactory.getLogger(GatewayApplication.class);

   public static void main(String[] args) {
      SpringApplication.run(GatewayApplication.class, args);
   }

   @GetMapping(value = "/token")
   public Mono<String> getHome(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
      return Mono.just(authorizedClient.getAccessToken().getTokenValue());
   }

   @GetMapping("https://piotrminkowski.com/")
   public Mono<String> index(WebSession session) {
      return Mono.just(session.getId());
   }

}

Scénario de test Spring Cloud Gateway OAuth2

Tout d’abord, regardons l’image qui illustre notre cas d’utilisation. Nous appelons POST /login Point final sur la passerelle (1). Après avoir reçu la demande de connexion Spring Cloud Gateway, essayez d’obtenir le jeton d’accès auprès du serveur d’autorisation (2). Ensuite, Keycloak renvoie le jeton d’accès JWT. En conséquence, Spring Cloud Gateway appelle cela userinfo Point final (3). Après avoir reçu la réponse, une session Web sera créée et Authentication Haricot. Enfin, l’application de passerelle renvoie un identifiant de session au client externe (4). Le client externe utilise un cookie d’ID de session pour autoriser les demandes. Il appelle GET ping du callme Application (5). le gateway L’application transmet la demande au service aval (6). Cependant, le cookie est supprimé et remplacé par un jeton d’accès JWT. le callme L’application vérifie un jeton entrant (7). Enfin il revient 200 OK Réponse, si le client est autorisé à appeler le point final (8). Sinon, il sera retourné 403 Forbidded.

spring-cloud-gateway-oauth2-login

Nous pouvons commencer les tests dans le navigateur Web. Appelons-les d’abord login Point final. Nous devons fournir aux clients keycloak-with-test-scope et keycloak-without-test-scope. Nous utiliserons le client keycloak-with-test-scope.

spring-cloud-gateway-oauth2-login

Ensuite, la porte nous transmet à la cape des clés login Page. Nous pouvons utiliser les informations d’identification fournies lors de la création du conteneur Keycloak.

Après une connexion réussie, la passerelle exécute la procédure d’autorisation OAuth2. Finalement, il nous redirige vers la page principale. La page principale n’est qu’une méthode index au sein du contrôleur. L’ID de session en cours est renvoyé.

Nous pouvons également utiliser un autre point de terminaison implémenté sur la passerelle – GET /token. Il renvoie le jeton d’accès JWT actuel.

$ curl http://localhost:8080/token -H "Cookie: SESSION=9bf852f1-6e00-42f8-a9a2-3cbdced33993"
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0RWpwdkVtQ1ZDZ1VDUm41Y2NJeXRiank0RnR0RXpBRXVrMURoZDRTT0RFIn0.eyJleHAiOjE2MDIyMzM5MTksImlhdCI6MTYwMjIz
MzAxOSwiYXV0aF90aW1lIjoxNjAyMjMzMDE5LCJqdGkiOiIyYWQzYjczNy1mZTdhLTQ3NGUtODhhYy01MGZjYzEzOTlhYTQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMv
bWFzdGVyIiwiYXVkIjpbIm1hc3Rlci1yZWFsbSIsImFjY291bnQiXSwic3ViIjoiOWVhMDAyYmQtOTQ4Ni00Njk0LWFhYzUtN2IyY2QwNzc2MTZiIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3ByaW5n
LWNsb3VkLWdhdGV3YXkiLCJzZXNzaW9uX3N0YXRlIjoiMDRhNzQ4YzUtOTA1My00ZmZmLWJjYzctNWY5MThjMzYwZGE4IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjcmVhdGUt
cmVhbG0iLCJST0xFX1RFTExFUiIsIm9mZmxpbmVfYWNjZXNzIiwiYWRtaW4iLCJURUxMRVIiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7Im1hc3Rlci1yZWFsbSI6eyJy
b2xlcyI6WyJ2aWV3LWlkZW50aXR5LXByb3ZpZGVycyIsInZpZXctcmVhbG0iLCJtYW5hZ2UtaWRlbnRpdHktcHJvdmlkZXJzIiwiaW1wZXJzb25hdGlvbiIsImNyZWF0ZS1jbGllbnQiLCJtYW5hZ2Ut
dXNlcnMiLCJxdWVyeS1yZWFsbXMiLCJ2aWV3LWF1dGhvcml6YXRpb24iLCJxdWVyeS1jbGllbnRzIiwicXVlcnktdXNlcnMiLCJtYW5hZ2UtZXZlbnRzIiwibWFuYWdlLXJlYWxtIiwidmlldy1ldmVu
dHMiLCJ2aWV3LXVzZXJzIiwidmlldy1jbGllbnRzIiwibWFuYWdlLWF1dGhvcml6YXRpb24iLCJtYW5hZ2UtY2xpZW50cyIsInF1ZXJ5LWdyb3VwcyJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5h
Z2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIFRFU1QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJl
ZF91c2VybmFtZSI6InBpb21pbiJ9.X8XfIHiAiR1YMjiJza75aC294qLwi83RrUU2phorM7FP4phq3h-lx80Zu5xqTrMqMC1-RbHBnX-oUTbs4ViS3DziZlDvoRajdkrh6UTiK5oWgoRW-4qsH5L4X1W
bRfoBZgyHFRSnhaCO4CLgjCyEgeLUR5A-JWY-OMYQIOAxxHB2GwE3MNFfLWeqpmS1AWU8fL0giFFXFDfa1_XZEKgnqe1S75Ps_z8B1sfNfvNpz8daJ8omzXrt6I6TSa0FE3iiZ7Qx18mtkbx-iPuFqDD
RT6DGU-Hing9LnGuOt3Yas-WYdN7PKBigvIZv0LyvRFcilRJQBjOdVfEddL3OQ0rmEg

Juste pour vérifier cela, vous pouvez décoder un jeton JWT sur l’ordinateur https://jwt.io Page? ˅.

spring-cloud-gateway-jwt-decoded

Enfin, passons au point de terminaison mis à disposition par le callme Application. Nous définissons la session Cookie dans l’en-tête de la demande. Le point de terminaison renvoie une liste des domaines attribués à l’utilisateur actuel. Utilisateurs de la portée uniquement TEST peut appeler la méthode.

$ curl http://localhost:8080/callme/ping -H "Cookie: SESSION=9bf852f1-6e00-42f8-a9a2-3cbdced33993"
Scopes: [SCOPE_profile, SCOPE_email, SCOPE_TEST]

Conclusion

Dans cet article, nous avons abordé des problèmes importants liés à la sécurité des microservices. Je vous ai montré comment activer la prise en charge d’OAuth2 pour Spring Cloud Gateway et l’intégrer à Keycloak. Nous avons implémenté des mécanismes tels que la connexion OAuth2, le relais de jeton et le serveur de ressources OAuth2. Les mécanismes de relais de jetons sont entièrement migrés de Spring Cloud Security vers Spring Cloud Gateway. Amusez-vous bien 🙂



Source link

Recent Posts