web-dev-qa-db-fra.com

Comment configurer oAuth2 avec le flux de mots de passe avec Swagger ui dans l'application d'amorçage au printemps

J'ai Spring Spring Rest Api (ressources) qui utilise un autre serveur d'autorisation Spring Boot, j'ai ajouté Swagger config à l'application de ressources pour obtenir une plateforme de documentation/test agréable et rapide pour le reste de l'API. ma configuration Swagger ressemble à ceci:

@Configuration
@EnableSwagger2
public class SwaggerConfig {    

    @Autowired
    private TypeResolver typeResolver;

    @Value("${app.client.id}")
    private String clientId;
    @Value("${app.client.secret}")
    private String clientSecret;
    @Value("${info.build.name}")
    private String infoBuildName;

    public static final String securitySchemaOAuth2 = "oauth2";
    public static final String authorizationScopeGlobal = "global";
    public static final String authorizationScopeGlobalDesc = "accessEverything";

    @Bean
    public Docket api() { 

        List<ResponseMessage> list = new Java.util.ArrayList<ResponseMessage>();
        list.add(new ResponseMessageBuilder()
                .code(500)
                .message("500 message")
                .responseModel(new ModelRef("JSONResult«string»"))
                .build());
        list.add(new ResponseMessageBuilder()
                .code(401)
                .message("Unauthorized")
                .responseModel(new ModelRef("JSONResult«string»"))
                .build());


        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())     
          .build()
          .securitySchemes(Collections.singletonList(securitySchema()))
          .securityContexts(Collections.singletonList(securityContext()))
          .pathMapping("/")
          .directModelSubstitute(LocalDate.class,String.class)
          .genericModelSubstitutes(ResponseEntity.class)
          .alternateTypeRules(
              newRule(typeResolver.resolve(DeferredResult.class,
                      typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
                  typeResolver.resolve(WildcardType.class)))
          .useDefaultResponseMessages(false)
          .apiInfo(apiInfo())
          .globalResponseMessage(RequestMethod.GET,list)
          .globalResponseMessage(RequestMethod.POST,list);
    }


    private OAuth securitySchema() {

        List<AuthorizationScope> authorizationScopeList = newArrayList();
        authorizationScopeList.add(new AuthorizationScope("global", "access all"));

        List<GrantType> grantTypes = newArrayList();
        final TokenRequestEndpoint tokenRequestEndpoint = new TokenRequestEndpoint("http://server:port/oauth/token", clientId, clientSecret);
        final TokenEndpoint tokenEndpoint = new TokenEndpoint("http://server:port/oauth/token", "access_token");
        AuthorizationCodeGrant authorizationCodeGrant = new AuthorizationCodeGrant(tokenRequestEndpoint, tokenEndpoint);

        grantTypes.add(authorizationCodeGrant);

        OAuth oAuth = new OAuth("oauth", authorizationScopeList, grantTypes);

        return oAuth;
    }


    private SecurityContext securityContext() {
        return SecurityContext.builder().securityReferences(defaultAuth())
                .forPaths(PathSelectors.ant("/api/**")).build();
    }

    private List<SecurityReference> defaultAuth() {

        final AuthorizationScope authorizationScope =
                new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);
        final AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Collections
                .singletonList(new SecurityReference(securitySchemaOAuth2, authorizationScopes));
    }



    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(“My rest API")
                .description(" description here … ”)
                .termsOfServiceUrl("https://www.example.com/")
                .contact(new Contact(“XXXX XXXX”,
                                     "http://www.example.com", “[email protected]”))
                .license("license here”)
                .licenseUrl("https://www.example.com")
                .version("1.0.0")
                .build();
    }

}

Pour obtenir le jeton d'accès du serveur d'autorisations, j'utilise http POST] pour ce lien avec une autorisation de base dans l'en-tête de clientid/clientpass:

http://server:port/oauth/token?grant_type=password&username=<username>&password=<password>

la réponse est quelque chose comme:

{
    "access_token": "e3b98877-f225-45e2-add4-3c53eeb6e7a8",
    "token_type": "bearer",
    "refresh_token": "58f34753-7695-4a71-c08a-d40241ec3dfb",
    "expires_in": 4499,
    "scope": "read trust write"
}

dans l'interface utilisateur Swagger, je peux voir un bouton d'autorisation, qui ouvre une boîte de dialogue pour effectuer la demande d'autorisation, mais il ne fonctionne pas et me dirige vers un lien comme suit, 

http://server:port/oauth/token?response_type=code&redirect_uri=http%3A%2F%2Fserver%3A8080%2Fwebjars%2Fspringfox-swagger-ui%2Fo2c.html&realm=undefined&client_id=undefined&scope=global%2CvendorExtensions&state=oauth

qu'est-ce qui me manque ici?

 Swagger UI has an Authorisation button

6
Hasson

Après 8 mois, le flux de mots de passe est enfin pris en charge dans l'interface utilisateur Swagger. Voici le code final et les paramètres qui fonctionnent pour moi:

1) Swagger Config:

package com.example.api;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.GrantType;
import springfox.documentation.service.OAuth;
import springfox.documentation.service.ResourceOwnerPasswordCredentialsGrant;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import Java.util.Collections;
import Java.util.List;

import static com.google.common.collect.Lists.*;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value("${app.client.id}")
    private String clientId;
    @Value("${app.client.secret}")
    private String clientSecret;
    @Value("${info.build.name}")
    private String infoBuildName;

    @Value("${Host.full.dns.auth.link}")
    private String authLink;

    @Bean
    public Docket api() {

        List<ResponseMessage> list = new Java.util.ArrayList<>();
        list.add(new ResponseMessageBuilder().code(500).message("500 message")
                .responseModel(new ModelRef("Result")).build());
        list.add(new ResponseMessageBuilder().code(401).message("Unauthorized")
                .responseModel(new ModelRef("Result")).build());
        list.add(new ResponseMessageBuilder().code(406).message("Not Acceptable")
                .responseModel(new ModelRef("Result")).build());

        return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any()).build().securitySchemes(Collections.singletonList(securitySchema()))
                .securityContexts(Collections.singletonList(securityContext())).pathMapping("/")
                .useDefaultResponseMessages(false).apiInfo(apiInfo()).globalResponseMessage(RequestMethod.GET, list)
                .globalResponseMessage(RequestMethod.POST, list);



    }

    private OAuth securitySchema() {

        List<AuthorizationScope> authorizationScopeList = newArrayList();
        authorizationScopeList.add(new AuthorizationScope("read", "read all"));
        authorizationScopeList.add(new AuthorizationScope("trust", "trust all"));
        authorizationScopeList.add(new AuthorizationScope("write", "access all"));

        List<GrantType> grantTypes = newArrayList();
        GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant(authLink+"/oauth/token");

        grantTypes.add(creGrant);

        return new OAuth("oauth2schema", authorizationScopeList, grantTypes);

    }

    private SecurityContext securityContext() {
        return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant("/user/**"))
                .build();
    }

    private List<SecurityReference> defaultAuth() {

        final AuthorizationScope[] authorizationScopes = new AuthorizationScope[3];
        authorizationScopes[0] = new AuthorizationScope("read", "read all");
        authorizationScopes[1] = new AuthorizationScope("trust", "trust all");
        authorizationScopes[2] = new AuthorizationScope("write", "write all");

        return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));
    }

    @Bean
    public SecurityConfiguration securityInfo() {
        return new SecurityConfiguration(clientId, clientSecret, "", "", "", ApiKeyVehicle.HEADER, "", " ");
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title("My API title").description("")
                .termsOfServiceUrl("https://www.example.com/api")
                .contact(new Contact("Hasson", "http://www.example.com", "[email protected]"))
                .license("Open Source").licenseUrl("https://www.example.com").version("1.0.0").build();
    }

}

2) dans POM, utilisez cette version 2.7.0 de Swagger UI:

    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.7.0</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.7.0</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-bean-validators</artifactId>
        <version>2.7.0</version>
    </dependency>

3) dans le fichier application.properties, ajoutez les propriétés suivantes:

Host.full.dns.auth.link=http://oauthserver.example.com:8081
app.client.id=test-client
app.client.secret=clientSecret
auth.server.schem=http

4) dans le serveur d'autorisation, ajoutez un filtre CORS: 

package com.example.api.oauth2.oauth2server;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import Java.io.IOException;

/**
 * Allows cross Origin for testing swagger docs using swagger-ui from local file
 * system
 */
@Component
public class CrossOriginFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(CrossOriginFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        // Called by the web container to indicate to a filter that it is being
        // placed into service.
        // We do not want to do anything here.
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        log.info("Applying CORS filter");
        HttpServletResponse response = (HttpServletResponse) resp;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "0");
        chain.doFilter(req, resp);
    }

    @Override
    public void destroy() {

        // Called by the web container to indicate to a filter that it is being
        // taken out of service.
        // We do not want to do anything here.
    }
}

Si vous exécutez avec ces paramètres, vous obtiendrez le bouton d'autorisation dans le lien http://apiServer.example.com:8080/swagger-ui.html#/ (si vous exécutez sur 8080) comme suit:

 enter image description here

Ensuite, lorsque vous cliquez sur le bouton d'autorisation, vous obtenez le dialogue suivant, ajoutez les données de votre nom d'utilisateur/mot de passe, ainsi que l'ID et le secret du client. Le type doit être le corps de la demande. Je ne sais pas pourquoi, mais c'est ce qui fonctionne avec moi, bien que j’ai pensé que c’était une authentification de base, c’est ainsi que Swagger-ui fonctionne avec le flux de mots de passe et que tous vos points de terminaison d’API fonctionnent à nouveau. Bonne fanfaronnade !!! :) 

 enter image description here

17
Hasson

Je ne suis pas sûr de savoir quel était le problème pour vous, mais le bouton Autoriser fonctionne pour moi pour swagger version 2.7.0, bien que je doive obtenir le jeton JWT manuellement. 

Je fais d'abord un jeton pour le jeton d'authentification puis j'insère le jeton comme ci-dessous, 

 enter image description here

La clé ici est que mes jetons sont JWT et que je ne pouvais pas insérer de valeur de jeton après Bearer ** et changer le nom de ** api_key en Authorization et que j'ai réalisé avec la configuration Java ci-dessous, 

@Bean
    public SecurityConfiguration securityInfo() {
        return new SecurityConfiguration(null, null, null, null, "", ApiKeyVehicle.HEADER,"Authorization",": Bearer");
    }

Il semble y avoir un bug dans swagger à propos de scope separator qui est par défaut :. Dans ma configuration, j'ai essayé de le modifier en : Bearer mais cela ne se produit pas, je dois donc saisir cela sur l'interface utilisateur. 

1
Sabir Khan

Il s'agit d'un bogue sur swagger-ui 2.6.1 qui envoie systématiquement la portée de vendorExtensions. De plus, les demandes sortent de la portée, ce qui entraîne le rejet des demandes. Puisque swagger ne peut pas obtenir le jeton d'accès, il ne peut pas passer oauth2

Mettre à jour sur maven devrait résoudre le problème. La version minimale devrait être 2.7.0

0
barisc

Jusqu'à présent, la meilleure façon de travailler avec oAuth2 Authorization est d'utiliser Swagger Editor. J'ai rapidement installé Swagger Editor dans Docker (à partir de ici ), puis utilisé le paramètre import pour télécharger le descripteur JSON de l'API (votre API devrait inclure CORS filtrer), je peux alors obtenir la documentation Swagger et une interface où je peux ajouter un jeton obtenu avec un client curl, postman ou Firefox.

Le lien que j'utilise maintenant ressemble à ceci

http://docker.example.com/#/?import=http://mywebserviceapi.example.com:8082/v2/api-docs&no-proxy

l'interface dans Swagger Editor pour entrer le jeton ressemble à ceci:

 enter image description here

s'il existe de meilleures solutions ou solutions de contournement, merci de poster votre réponse ici.

0
Hasson