web-dev-qa-db-ja.com

Spring Boot RestアプリケーションでSwagger UIを使用してパスワードフローでoAuth2を構成する方法

私は別のSpring Boot Authorization Serverを使用するSpring Boot Rest API(リソース)を持っています。Swagger設定をリソースアプリケーションに追加して、残りのAPIのすてきで迅速なドキュメント/テストプラットフォームを取得しました。私のSwagger構成は次のようになります。

@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();
    }

}

Authorizationサーバーからアクセストークンを取得する方法は、http POST to this link to this link with basic authorization in the header in clientid/clientpass:

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

応答は次のようなものです:

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

swagger UIには、承認リクエストを行うためのダイアログを開く[承認]ボタンが表示されますが、機能しておらず、次のようにリンクが表示されます。

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

ここで何が欠けていますか?

Swagger UI has an Authorisation button

11
Hasson

8か月後、最後にパスワードフローがSwagger UIでサポートされます。これが私にとって有効な最終的なコードと設定です。

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)POMでは、このSwagger UIバージョン2.7.0を使用します。

    <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)application.propertiesに次のプロパティを追加します。

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

4)認可サーバーに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.
    }
}

これらの設定で実行すると、リンク http://apiServer.example.com:8080/swagger-ui.html#/ (8080で実行する場合)の承認ボタンが次のように表示されます。 :

enter image description here

次に、承認ボタンをクリックすると、次のダイアログが表示され、ユーザー名/パスワードとクライアントIDとクライアントシークレットのデータを追加します。タイプはリクエストの本文である必要があります。理由はわかりませんが、これは機能します私と一緒に、これはクライアントシークレットが送信される方法であるため、基本的な認証であると考えましたが、いずれにせよ、これはSwagger-uiがパスワードフローで機能し、すべてのAPIエンドポイントが再び機能する方法です。よろしくお願いします!!! :)

enter image description here

17
Hasson

何が問題だったのかわかりませんが、JWTトークンを取得する必要がありますが、AuthorizeボタンがSwaggerバージョン2.7.0で機能します。手動で。

最初に認証トークンをヒットし、次に以下のようにトークンを挿入します。

enter image description here

ここで重要なのは、私のトークンはJWTであり、Bearer **の後にトークン値を挿入できず、** api_keyの名前をAuthorizationそして私が以下で達成したJava設定、

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

スコープセパレーターに関するswaggerにバグがあるようですが、デフォルトではです。私の構成では、それを: Bearerに変更しようとしましたが、それは発生していないため、UIで入力する必要があります。

1
Sabir Khan

これは、毎回vendorExtensionsスコープを送信するswagger-ui 2.6.1のバグです。そして、それはリクエストがスコープ外になり、リクエストが拒否される原因となります。 swaggerはアクセストークンを取得できないため、oauth2を渡すことができません

Mavenでアップグレードすると問題が解決するはずです。最小バージョンは2.7.0である必要があります

0
barisc

これまでのところ、oAuth2 Authorizationを使用する最善の方法は、Swagger Editorを使用することです。SwaggerEditorをDockerにすばやくインストールし( here から)、次にimportパラメーターを使用してAPI JSON記述子(API CORSフィルターを含める必要があります)、Swaggerドキュメントと、curl、postman、またはFirefoxレストクライアントを使用して取得したトークンを追加できるインターフェイスを取得できます。

今使っているリンクはこんな感じ

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

トークンを入力するSwagger Editorのインターフェースは次のようになります。

enter image description here

より良い解決策や回避策がある場合は、ここに回答を投稿してください。

0
Hasson