web-dev-qa-db-fra.com

Intégration Keycloak dans Swagger

J'ai un backend protégé par Keycloak auquel j'aimerais accéder via swagger-ui. Keycloak fournit le flux de code implicite et d'accès oauth2, mais je n'ai pas pu le faire fonctionner. Actuellement, la documentation de Keycloak fait défaut concernant l'url à utiliser pour authorizationUrl et tokenUrl dans swagger.json .

Chaque domaine 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 keycloak js dans swagger-ui index.html 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 aussi essayé quelque chose comme ça après 'Login Successful'

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

Mais cela ne fonctionne pas non plus.

Avez-vous des suggestions sur la façon d'intégrer l'authentification Keycloak dans Swagger?

36
melistik

Swagger-ui peut s'intégrer à keycloak en utilisant le mode d'authentification implicit. 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 fanfaron doit référencer une définition de 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 référencer un autre paramètre: Avec les js purs, vous pouvez utiliser dans le 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 domaine keycloak
  • Les portées sont quelque chose que vous pouvez définir selon vos besoins
  • clientId est un client paramétré avec le mode implicit sur le domaine keycloak
  • le paramètre supplémentaire nonce doit ê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. Cela se 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 {
    ...

alors vous pouvez configurer une classe 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'utilisez pas encore)
  • clientId que vous devez configurer en fonction du client que vous avez configuré dans keycloak
  • basePackage: Vous devez définir le package dans lequel tous vos contrôleurs sont
  • 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 de terminaison d'autorisation de votre domaine keycloak
  • scopeItems: les étendues que vous souhaitez pour cette authentification.

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

6
wargre

J'avais du mal avec cette configuration au cours des 2 derniers jours. Enfin obtenu une solution de travail pour ceux qui ne peuvent pas résoudre.

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 appuyez sur http: // localhost: [port]/[app_name] /swagger-ui.html .

Cliquez sur le bouton Autoriser: Swagger Authorize Button

Cela devrait présenter un modal pour confirmer vos paramètres de keycloak.

Cliquez à nouveau sur le bouton Autoriser. Vous devez être redirigé vers un écran de connexion.

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

5
user1653042

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 Keycloak pour le client que vous utilisez.

Un inconvénient est que l'utilisateur est toujours demandé pour client_id dans le modal lorsqu'il clique sur le bouton "Autoriser" dans Swagger UI. La valeur entrée par l'utilisateur peut être remplacée en ajoutant un paramètre de requête ?client_id=YOUR-CLIENT-ID à l'autorisationUrl mais c'est un peu le hack sale et le modal est toujours montré à l'utilisateur. Lors de l'exécution de swagger-ui dans docker - la variable env OAUTH_CLIENT_ID peut être fournie au conteneur pour définir la valeur client_id par défaut pour le modal. Pour le déploiement sans docker, reportez-vous à l'approche de @ wargre avec la modification du fichier index.html (vous ne savez pas s'il existe une meilleure méthode).

Pour l'exemple de 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/

0
Nikita Mendelbaum