簡體   English   中英

Swagger 當我點擊執行時無限加載

[英]Swagger endless loading when i click in Execute

我正在使用 Swagger 和 keycloak。 單擊“執行”按鈕時出現以下錯誤。

當我單擊 swagger 上的執行時,我看到加載圖像,並且網絡分接頭上沒有任何請求,因為我在控制台上有錯誤。

我將添加我的配置代碼和 YML 文件,讓您看到我在做什么。

任何人都可以幫助我,好嗎?

這是控制台中的 output:

這是控制台中的輸出

這是控制台點擊中的錯誤代碼(其生成的代碼):

這是控制台點擊中的錯誤代碼

這是 swagger UI 頁面:

這是招搖的用戶界面頁面

這是 swagger 配置:

@Slf4j
@Configuration
@TbCoreComponent
public class SwaggerConfiguration {

    @Value("${swagger.api_path_regex}")
    private String apiPathRegex;
    @Value("${swagger.security_path_regex}")
    private String securityPathRegex;
    @Value("${swagger.non_security_path_regex}")
    private String nonSecurityPathRegex;
    @Value("${swagger.title}")
    private String title;
    @Value("${swagger.description}")
    private String description;
    @Value("${swagger.contact.name}")
    private String contactName;
    @Value("${swagger.contact.url}")
    private String contactUrl;
    @Value("${swagger.contact.email}")
    private String contactEmail;
    @Value("${swagger.version}")
    private String version;
    @Value("${app.version:unknown}")
    private String appVersion;
    
    // Used to get token from Keyclaok
    @Value("${swagger.auth.token_url}")
    private String keycloakAuthTokenUrl;
    @Value("${security.keycloak.realm}")
    private String keycloakRealm;
    @Value("${security.keycloak.clientId}")
    private String keyclaokAuthCliendId;

    @Bean
    public Docket yousefApi() {
        TypeResolver typeResolver = new TypeResolver();
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("Hammad")
                .apiInfo(apiInfo())
                .additionalModels(
                        typeResolver.resolve(ThingsboardErrorResponse.class),
                        typeResolver.resolve(ThingsboardCredentialsExpiredResponse.class)
                )
                .select()
                .paths(apiPaths())
                .paths(any())
                .build()
                .globalResponses(HttpMethod.GET, defaultErrorResponses(false))
                .globalResponses(HttpMethod.POST, defaultErrorResponses(true))
                .globalResponses(HttpMethod.DELETE, defaultErrorResponses(false))
                .securitySchemes(newArrayList(apiKey()))
                .securityContexts(newArrayList(securityContext()))
                .enableUrlTemplating(true);
    }


    @Bean
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
    ApiListingBuilderPlugin loginEndpointListingBuilder() {
        return new ApiListingBuilderPlugin() {
            @Override
            public void apply(ApiListingContext apiListingContext) {
                if (apiListingContext.getResourceGroup().getGroupName().equals("Hammad")) {
                    ApiListing apiListing = apiListingContext.apiListingBuilder().build();
                    if (apiListing.getResourcePath().equals(keycloakAuthTokenUrl)) {
                        apiListingContext.apiListingBuilder().tags(Set.of(new Tag("login-endpoint", "Login Endpoint")));
                        apiListingContext.apiListingBuilder().description("Login Endpoint");
                    }
                }
            }

            @Override
            public boolean supports(@NotNull DocumentationType delimiter) {
                return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
            }
        };
    }

    @Bean
    UiConfiguration uiConfig() {
        return UiConfigurationBuilder.builder()
                .deepLinking(true)
                .displayOperationId(false)
                .defaultModelsExpandDepth(1)
                .defaultModelExpandDepth(1)
                .defaultModelRendering(ModelRendering.EXAMPLE)
                .displayRequestDuration(false)
                .docExpansion(DocExpansion.NONE)
                .filter(false)
                .maxDisplayedTags(null)
                .operationsSorter(OperationsSorter.ALPHA)
                .showExtensions(false)
                .showCommonExtensions(false)
                .supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
                .validatorUrl(null)
                .persistAuthorization(true)
                .syntaxHighlightActivate(true)
                .syntaxHighlightTheme("agate")
                .build();
    }

    private ApiKey apiKey() {
        return new ApiKey("Bearer", "X-Authorization", "header");
    }
    
    private OAuth securityScheme() {
        List<GrantType> grantTypes = newArrayList(new ResourceOwnerPasswordCredentialsGrant(keycloakAuthTokenUrl));
        return new OAuth("KeycloakAuth", new ArrayList<>(), grantTypes);
    }
    
    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .operationSelector(securityPathOperationSelector())
                .build();
    }
    
    private Predicate<String> apiPaths() {
        return regex(apiPathRegex);
    }

    private Predicate<OperationContext> securityPathOperationSelector() {
        return new SecurityPathOperationSelector(securityPathRegex, nonSecurityPathRegex);
    }

    private AuthorizationScope[] scopes() {
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[3];
        authorizationScopes[0] = new AuthorizationScope(Authority.SYS_ADMIN.name(), "System administrator");
        authorizationScopes[1] = new AuthorizationScope(Authority.TENANT_ADMIN.name(), "Tenant administrator");
        authorizationScopes[2] = new AuthorizationScope(Authority.CUSTOMER_USER.name(), "Customer");
        return authorizationScopes;
    }
    
    List<SecurityReference> defaultAuth() {
        return newArrayList(new SecurityReference("KeycloakAuth", scopes()), new SecurityReference("Bearer", scopes()));
    }

    private ApiInfo apiInfo() {
        String apiVersion = version;
        if (StringUtils.isEmpty(apiVersion)) {
            apiVersion = appVersion;
        }
        return new ApiInfoBuilder()
                .title(title)
                .description(description)
                .contact(new Contact(contactName, contactUrl, contactEmail))
                .version(apiVersion)
                .build();
    }

    /** Helper methods **/

    private List<Response> defaultErrorResponses(boolean isPost) {
        return List.of(
                errorResponse("400", "Bad Request",
                        ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)),
                errorResponse("401", "Unauthorized",
                        ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
                errorResponse("403", "Forbidden",
                        ThingsboardErrorResponse.of("You don't have permission to perform this operation!",
                        ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)),
                errorResponse("404", "Not Found",
                        ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)),
                errorResponse("429", "Too Many Requests",
                        ThingsboardErrorResponse.of("Too many requests for current tenant!",
                        ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS))
        );
    }

    private Response errorResponse(String code, String description, ThingsboardErrorResponse example) {
        return errorResponse(code, description,  List.of(errorExample("error-code-" + code, description, example)));
    }

    private Response errorResponse(String code, String description, List<Example> examples) {
        return errorResponse(code, description, examples, ThingsboardErrorResponse.class);
    }

    private Response errorResponse(String code, String description, List<Example> examples,
                                   Class<? extends ThingsboardErrorResponse> errorResponseClass) {
        return new ResponseBuilder()
                .code(code)
                .description(description)
                .examples(examples)
                .representation(MediaType.APPLICATION_JSON)
                .apply(classRepresentation(errorResponseClass, true))
                .build();
    }

    private Example errorExample(String id, String summary, ThingsboardErrorResponse example) {
        return new ExampleBuilder()
                .mediaType(MediaType.APPLICATION_JSON_VALUE)
                .summary(summary)
                .id(id)
                .value(example).build();
    }

    private Consumer<RepresentationBuilder> classRepresentation(Class<?> clazz, boolean isResponse) {
        return r -> r.model(
                m ->
                        m.referenceModel(ref ->
                                ref.key(k ->
                                        k.qualifiedModelName(q ->
                                                q.namespace(clazz.getPackageName())
                                                        .name(clazz.getSimpleName())).isResponse(isResponse)))
        );
    }

    private static class SecurityPathOperationSelector implements Predicate<OperationContext> {

        private final Predicate<String> securityPathSelector;

        SecurityPathOperationSelector(String securityPathRegex, String nonSecurityPathRegex) {
            this.securityPathSelector = (not(regex(nonSecurityPathRegex)));
        }

        @Override
        public boolean test(OperationContext operationContext) {
            return this.securityPathSelector.test(operationContext.requestMappingPattern());
        }
    }


}

這是 YML 文件:

security:
  keycloak:
    mode: "${KEYCLOAK_MODE:external}" # when 'internal' => the user is handled with our internal keycloak server, when 'external' => the user is handled by an external Keycloak server
    serverUrl: "${KEYCLOAK_URL:http://localhost:9080/auth}"
    realm: "${KEYCLOAK_REALM:TTT}"
    clientId: "${KEYCLOAK_TOKEN_ISSUER_CLIENT:test_app}"
    clientSecret: "${KEYCLOAK_CLIENT_SECRET:000000000-4710-0000-b483-63b2ca1fe98b}"

    # the below info is needed in internal mode, The manager user of the realm, and the Sys_Admin email
    # realmAdminUsername: "${KEYCLOAK_REALM_ADMIN_USERNAME:yousef-hammad}"
    # realmAdminPassword: "${KEYCLOAK_REALM_ADMIN_PASSWORD:toty@98}"
    # sysAdminUsername: "${KEYCLOAK_SYS_ADMIN_USERNAME:sysadmin@yousefco.net}" # Note that, the sysadmin will be used in a static way in unit test.
    
    # the below info is needed in external mode, The id and email of the Sys_Admin user in the system
    sysAdminUserId: "${KEYCLOAK_USER_ID:00000000-748b-4e89-87ef-71fd798c3123}"
    sysAdminUsername: "${KEYCLOAK_USER_EMAIL:yousef11@yousefco.com}"
swagger:
  auth:
    token_url: ${security.keycloak.serverUrl}/realms/${security.keycloak.realm}/protocol/openid-connect/token/
    auth_url: ${security.keycloak.serverUrl}/realms/${security.keycloak.realm}/protocol/openid-connect/auth/
  api_path_regex: "${SWAGGER_API_PATH_REGEX:/api/(customer|device|user|tenant).*}"
  security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api/(customer|device|user|tenant).*}"
  non_security_path_regex: "${SWAGGER_NON_SECURITY_PATH_REGEX:/api/(?:noauth|v1)/.*}"
  title: "${SWAGGER_TITLE:yousefCo REST API}"
  description: "${SWAGGER_DESCRIPTION: yousefCo open-source IoT platform REST API documentation.}"
  contact:
    name: "${SWAGGER_CONTACT_NAME:yousefCo Team}"
    url: "${SWAGGER_CONTACT_URL:http://iot.test.net}"
    email: "${SWAGGER_CONTACT_EMAIL:info@gmail.com}"
  license:
    title: "${SWAGGER_LICENSE_TITLE:Apache License Version 2.0}"
    url: "${SWAGGER_LICENSE_URL:https://github.com/yousef/yousef/blob/master/LICENSE}"
  version: "${SWAGGER_VERSION:}"

    

我認為您在 swagger 版本 2 和 3 之間混合了配置,您的整體設置配置為在 Swagger 3.0 中使用,所以

首先,您必須將DocumentationType更改為OAS_30

二、使用ApiKey安全方案時,注意在調用API時,字段名會被用作header,所以要改成:

private ApiKey apiKey() {
    return new ApiKey("X-Authorization", "AnyNameYouWant", "header");
}

請注意,您必須在令牌之前添加Bearer詞,還必須將名稱SecurityReference更改為X-Authorization

第三,如果您需要配置OAuth2安全方案,而不是使用OAuth class,請使用OAuth2Scheme構建器 class 和password流程,如下所示:

return OAuth2Scheme.OAUTH2_PASSWORD_FLOW_BUILDER
        .name("KeycloakAuth")
        .scopes(newArrayList(scopes()))
        .tokenUrl(keycloakAuthTokenUrl)
        .refreshUrl(keycloakAuthTokenUrl)
        .build();

不要忘記將SecurityReference中的相同范圍添加到OAuth2Scheme范圍中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM