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?
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 keycloakclientId
est un client paramétré avec le mode implicit
sur le domaine keycloaknonce
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 keycloakbasePackage
: Vous devez définir le package dans lequel tous vos contrôleurs sontLoginEndpoint
: qui doit être le point de terminaison d'autorisation de votre domaine keycloakscopeItems
: 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, ...
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é.
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/