46 votes

Intégration de Keycloak dans Swagger

J'ai un backend protégé par Keycloak auquel je voudrais accéder via swagger-ui. Keycloak fournit l'oauth2 implicite et le flux de code d'accès, mais je n'ai pas réussi à le faire fonctionner. Actuellement, la documentation de Keycloak est insuffisante en ce qui concerne l'url qui doit être utilisée pour authorizationUrl y tokenUrl sur swagger.json .

Chaque royaume au sein de Keycloak offre une énorme liste d'url de configuration en accédant à http://keycloak.local/auth/realms/REALM/.well-known/openid-configuration

De plus, j'ai essayé d'intégrer directement le client js de keycloak dans l'index.html de swagger-ui en ajoutant les lignes suivantes :

<script src="keycloak/keycloak.js"></script>
<script>
  var keycloak = Keycloak('keycloak.json');
    keycloak.init({ onLoad: 'login-required' })
      .success(function (authenticated) {
        console.log('Login Successful');
        window.authorizations.add("oauth2", new ApiKeyAuthorization("Authorization", "Bearer " + keycloak.token, "header"));
      }).error(function () {
        console.error('Login Failed');
        window.location.reload();
      }
    );
 </script>

J'ai également essayé quelque chose comme ceci après "Connexion réussie".

swaggerUi.api.clientAuthorizations.add("key", new SwaggerClient.ApiKeyAuthorization("Authorization", "Bearer " + keycloak.token, "header"));

Mais cela ne fonctionne pas non plus.

Des suggestions sur la façon dont je peux intégrer l'authentification keycloak dans swagger ?

5 votes

Avez-vous réussi à faire fonctionner ce système ? J'ai le même problème en ce moment.

0 votes

Voulez-vous intégrer keycloak avec swagger-UI ? Avez-vous protégé votre définition swagger avec keycloak maintenant ? Peut-être je peux vous aider

14voto

wargre Points 3357

Swagger-ui peut s'intégrer à keycloak en utilisant l'option implicit mode d'authentification. Vous pouvez configurer oauth2 sur swagger-ui afin qu'il vous demande de vous authentifier au lieu de donner directement à swagger-ui le jeton d'accès.

1ère chose, votre swagger doit faire référence à une définition de la sécurité comme :

"securityDefinitions": {
    "oauth2": {
        "type":"oauth2",
        "authorizationUrl":"http://172.17.0.2:8080/auth/realms/master/protocol/openid-connect/auth",
        "flow":"implicit",
        "scopes": {
            "openid":"openid",
            "profile":"profile"
        }
    }
}

Ensuite, vous swagger-ui devez faire référence à un autre paramètre : Avec le js pur, vous pouvez utiliser dans la balise index.html

const ui = SwaggerUIBundle({ ...} );

ui.initOAuth({
    clientId: "test-uid",
    realm: "Master",
    appName: "swagger-ui",
    scopeSeparator: " ",
    additionalQueryStringParams: {"nonce": "132456"}
})

Dans ce code,

  • authorizationUrl est le point de terminaison d'autorisation sur votre royaume keycloak
  • Vous pouvez adapter la portée à vos besoins.
  • clientId est un client paramétré avec implicit mode sur le royaume de Keycloak
  • le paramètre supplémentaire nonce devrait être aléatoire, mais swagger-ui ne l'utilise pas encore.

J'ajoute ici un exemple si vous voulez faire tout cela sur Spring-boot :

Sur ce framework, vous utiliserez principalement swagger et swagger-ui web-jar de Springfox. Ceci est fait en ajoutant les dépendances :

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.8.0</version>
</dependency>

Swagger est activé en ajoutant l'annotation swagger2 sur votre classe principale :

@SpringBootApplication
@EnableSwagger2
public class TestSpringApplication {
    ...

vous pouvez alors mettre en place un Configuration comme ceci :

@Configuration
public class SwaggerConfigurer {

    @Bean
    public SecurityConfiguration securityConfiguration() {

        Map<String, Object> additionalQueryStringParams=new HashMap<>();
        additionalQueryStringParams.put("nonce","123456");

        return SecurityConfigurationBuilder.builder()
            .clientId("test-uid").realm("Master").appName("swagger-ui")
            .additionalQueryStringParams(additionalQueryStringParams)
            .build();
    }

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.testspring"))
            .paths(PathSelectors.any())
            .build().securitySchemes(buildSecurityScheme()).securityContexts(buildSecurityContext());
    }

    private List<SecurityContext> buildSecurityContext() {
        List<SecurityReference> securityReferences = new ArrayList<>();

        securityReferences.add(SecurityReference.builder().reference("oauth2").scopes(scopes().toArray(new AuthorizationScope[]{})).build());

        SecurityContext context = SecurityContext.builder().forPaths(Predicates.alwaysTrue()).securityReferences(securityReferences).build();

        List<SecurityContext> ret = new ArrayList<>();
        ret.add(context);
        return ret;
    }

    private List<? extends SecurityScheme> buildSecurityScheme() {
        List<SecurityScheme> lst = new ArrayList<>();
        // lst.add(new ApiKey("api_key", "X-API-KEY", "header"));

        LoginEndpoint login = new LoginEndpointBuilder().url("http://172.17.0.2:8080/auth/realms/master/protocol/openid-connect/auth").build();

        List<GrantType> gTypes = new ArrayList<>();
        gTypes.add(new ImplicitGrant(login, "acces_token"));

        lst.add(new OAuth("oauth2", scopes(), gTypes));
        return lst;
    }

    private List<AuthorizationScope> scopes() {
        List<AuthorizationScope> scopes = new ArrayList<>();
        for (String scopeItem : new String[]{"openid=openid", "profile=profile"}) {
            String scope[] = scopeItem.split("=");
            if (scope.length == 2) {
                scopes.add(new AuthorizationScopeBuilder().scope(scope[0]).description(scope[1]).build());
            } else {
                log.warn("Scope '{}' is not valid (format is scope=description)", scopeItem);
            }
        }

        return scopes;
    }
}

Il y a beaucoup de choses que vous pouvez mettre à jour dans ce code. C'est principalement le même qu'avant :

  • nonce qui devrait être une chose aléatoire (swagger-ui ne l'utilise pas encore)
  • clientId que vous devez configurer en fonction du client que vous avez configuré dans keycloak.
  • basePackage : Vous devez définir le paquet dans lequel se trouvent tous vos contrôleurs.
  • Si vous avez besoin d'une clé api, vous pouvez l'activer et l'ajouter à la liste des schémas de sécurité.
  • LoginEndpoint qui doit être le point d'accès d'autorisation de votre royaume keycloak.
  • scopeItems : les scopes que vous souhaitez pour cette authentification.

Il générera la même chose qu'avant : Mise à jour du swagger pour ajouter la securityDefinition et faire que swagger-UI prenne le paramètre pour clientId, nonce, ...

9voto

user1653042 Points 61

Je me suis battu avec cette installation pendant les deux derniers jours. J'ai enfin trouvé une solution pour ceux qui ne peuvent pas résoudre le problème.

pom.xml

    ...
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-spring-security-adapter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-spring-boot-starter</artifactId>
    </dependency>
    ...

Activer Swagger sur la classe principale

...    
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2
@EnableAsync
@EnableCaching
public class MainApplication {
  public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MainApplication.class);
    app.run(args);
  }
}

SwaggerConfig.java

package com.XXX.XXXXXXXX.app.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.AuthorizationCodeGrantBuilder;
import springfox.documentation.builders.OAuthBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Arrays;

import static springfox.documentation.builders.PathSelectors.regex;

/*
 * Setting up Swagger for spring boot
 * https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {

 @Value("${keycloak.auth-server-url}")
 private String AUTH_SERVER;

 @Value("${keycloak.credentials.secret}")
 private String CLIENT_SECRET;

 @Value("${keycloak.resource}")
 private String CLIENT_ID;

 @Value("${keycloak.realm}")
 private String REALM;

 private static final String OAUTH_NAME = "spring_oauth";
 private static final String ALLOWED_PATHS = "/directory_to_controllers/.*";
 private static final String GROUP_NAME = "XXXXXXX-api";
 private static final String TITLE = "API Documentation for XXXXXXX Application";
 private static final String DESCRIPTION = "Description here";
 private static final String VERSION = "1.0";

 @Bean
 public Docket taskApi() {
   return new Docket(DocumentationType.SWAGGER_2)
    .groupName(GROUP_NAME)
    .useDefaultResponseMessages(true)
    .apiInfo(apiInfo())
    .select()
    .paths(regex(ALLOWED_PATHS))
    .build()
    .securitySchemes(Arrays.asList(securityScheme()))
    .securityContexts(Arrays.asList(securityContext()));
 }

 private ApiInfo apiInfo() {
   return new 
     ApiInfoBuilder().title(TITLE).description(DESCRIPTION).version(VERSION).build();
 }

 @Bean
 public SecurityConfiguration security() {
   return SecurityConfigurationBuilder.builder()
    .realm(REALM)
    .clientId(CLIENT_ID)
    .clientSecret(CLIENT_SECRET)
    .appName(GROUP_NAME)
    .scopeSeparator(" ")
    .build();
 }

 private SecurityScheme securityScheme() {
   GrantType grantType =
    new AuthorizationCodeGrantBuilder()
        .tokenEndpoint(new TokenEndpoint(AUTH_SERVER + "/realms/" + REALM + "/protocol/openid-connect/token", GROUP_NAME))
        .tokenRequestEndpoint(
            new TokenRequestEndpoint(AUTH_SERVER + "/realms/" + REALM + "/protocol/openid-connect/auth", CLIENT_ID, CLIENT_SECRET))
        .build();

SecurityScheme oauth =
    new OAuthBuilder()
        .name(OAUTH_NAME)
        .grantTypes(Arrays.asList(grantType))
        .scopes(Arrays.asList(scopes()))
        .build();
return oauth;
 }

 private AuthorizationScope[] scopes() {
AuthorizationScope[] scopes = {
  new AuthorizationScope("user", "for CRUD operations"),
  new AuthorizationScope("read", "for read operations"),
  new AuthorizationScope("write", "for write operations")
};
return scopes;
}

private SecurityContext securityContext() {
return SecurityContext.builder()
    .securityReferences(Arrays.asList(new SecurityReference(OAUTH_NAME, scopes())))
    .forPaths(PathSelectors.regex(ALLOWED_PATHS))
    .build();
 }
}

Depuis le terminal, exécutez "mvnw spring-boot:run".

Ouvrez le navigateur et tapez http://localhost :[port]/[nom_de_l'application]/swagger-ui.html .

Cliquez sur le bouton Autoriser : Bouton d'autorisation Swagger

Cela devrait présenter une modale pour confirmer les paramètres de votre keycloak.

Cliquez une nouvelle fois sur le bouton Autoriser. Vous devriez être redirigé vers un écran de connexion.

Une fois les informations d'identification saisies et confirmées, vous serez redirigé vers Swagger-UI entièrement authentifié.

0 votes

D'où viennent les lunettes de visée ? Dans votre trousseau de clés, où avez-vous défini les paramètres utilisateur/lecture/écriture ?

4voto

Swagger-ui + Keycloak (ou tout autre fournisseur OAuth2) utilisant un flux implicite, modèle OpenAPI 3.0 :

components:
  ...
   securitySchemes:
    my_auth_whatever:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: https://MY-KEYCLOAK-HOST/auth/realms/MY-REALM-ID/protocol/openid-connect/auth
          scopes: {}
  ...
security:
  - my_auth_whatever: []

Assurez-vous que le flux implicite est activé dans les paramètres de Keycloak pour le client que vous utilisez.

L'inconvénient est que l'utilisateur est toujours invité à indiquer le numéro de client dans la modale lorsqu'il clique sur le bouton "Authorize" dans l'interface Swagger. La valeur que l'utilisateur saisit peut être écrasée en ajoutant le paramètre de requête ?client_id=YOUR-CLIENT-ID à l'authorizationUrl mais c'est un peu le hack sale et la modale est toujours montrée à l'utilisateur. Lors de l'exécution de swagger-ui dans docker - la var env OAUTH_CLIENT_ID peut être fournie au conteneur pour définir la valeur client_id par défaut pour la modale. Pour un déploiement sans docker, référez-vous à l'approche de @wargre en changeant l'index.html (je ne suis pas sûr qu'il y ait une meilleure méthode).

Pour l'exemple SwaggerAPI (OpenAPI 2.0), reportez-vous au premier extrait de code dans la réponse de @wargre et à ce document : https://swagger.io/docs/specification/2-0/authentication/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X