SWAGGER e spring

Configuração do SWAGGER

package br.com.ghnetsoft.comprasfood.sistemaapi.config;

import static java.util.Arrays.asList;
import static springfox.documentation.builders.PathSelectors.ant;
import static springfox.documentation.builders.RequestHandlerSelectors.basePackage;
import static springfox.documentation.spi.DocumentationType.SWAGGER_2;

import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(SWAGGER_2).select().apis(basePackage("br.com.ghnetsoft.comprasfood.sistemaapi.resource")).paths(ant("/**")).build().securityContexts(asList(securityContext())).securitySchemes(asList(new ApiKey("JWT", "Authorization", "header")));
    }

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

    private List<SecurityReference> defaultAuth() {
        return asList(new SecurityReference("JWT", new AuthorizationScope[]{ new AuthorizationScope("global", "accessEverything") }));
    }
}

Configuração do APP

package br.com.ghnetsoft.comprasfood.sistemaapi;

import java.security.SecureRandom;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;

import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
@EnableSwagger2
@EnableJpaAuditing
public class SistemaApiApplication {

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ObjectMapper getObjectMapper() {
        return new ObjectMapper();
    }

    public static void main(final String[] args) {
        avoidSSLValidation();
        SpringApplication.run(SistemaApiApplication.class, args);
    }

    private static void avoidSSLValidation() {
        final TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() {
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
            }

            @Override
            public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
            }
        } };
        try {
            final SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (final Exception e) {
        }
    }
}

O sistema tem várias bases de dados isto é.

Tem uma base principal que guarda os usuários, perfis e outras informações.

Tem as bases de informações de cada cliente

Assim quando o usuário logar, ele escolhe a rede e assim o spring conecta na base de dados e banco do cliente.

Assim tem algumas classes que fazem esta conexão no momento que o usuário loga.

TenantInterceptor

package br.com.ghnetsoft.comprasfood.sistemaapi.service.tenan;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.google.gson.Gson;

import br.com.ghnetsoft.comprasfood.sistemaapi.dto.RedeDTO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TenantInterceptor extends HandlerInterceptorAdapter {

    private final String apiRedeLogado;
    private final String apiRedePublico;
    private final HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).connectTimeout(Duration.ofSeconds(10)).build();
    private final Gson gson = new Gson();

    public TenantInterceptor(final String apiRedeLogado, final String apiRedePublico) {
        log.info("TenantInterceptor");
        log.info("apiRedeLogado: " + apiRedeLogado);
        log.info("apiRedePublico: " + apiRedePublico);
        this.apiRedeLogado = apiRedeLogado;
        this.apiRedePublico = apiRedePublico;
    }

    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
        log.info("preHandle");
        final String token = request.getHeader("Authorization");
        if (token == null) {
            return processaRedePublico(request);
        }
        final String logado = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
        log.info("logado: " + logado);
        final HttpRequest req = HttpRequest.newBuilder().uri(new URI(apiRedeLogado)).header("Authorization", token).GET().build();
        final HttpResponse<String> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
        final RedeDTO rede = gson.fromJson(resp.body(), RedeDTO.class);
        log.info("rede: " + rede.toString());
        final String tenantId = "tenant_" + logado + "_" + rede.getId();
        log.info("tenantId: " + tenantId);
        TenantContext.setTenantId(tenantId);
        TenantContext.setRede(rede);
        return true;
    }

    @Override
    public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception {
        log.info("postHandle");
        TenantContext.clear();
    }

    private boolean processaRedePublico(final HttpServletRequest request) throws URISyntaxException, IOException, InterruptedException {
        log.info("processaRedePublico");
        final String header = request.getHeader("chave-rede");
        if (header == null) {
            return false;
        }
        final String chaveRede = header.replace("bearer", "").trim();
        final HttpRequest req = HttpRequest.newBuilder().uri(new URI(apiRedePublico + "/buscar-chave/" + chaveRede + "/")).GET().build();
        final HttpResponse<String> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
        final RedeDTO rede = gson.fromJson(resp.body(), RedeDTO.class);
        log.info("rede: " + rede.toString());
        if (rede.getChave() == null) {
            return false;
        }
        final String tenantId = "tenant_" + chaveRede;
        log.info("tenantId: " + tenantId);
        TenantContext.setTenantId(tenantId);
        TenantContext.setRede(rede);
        return true;
    }
}

TenantConnectionProvider

package br.com.ghnetsoft.comprasfood.sistemaapi.service.tenan;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.hibernate.engine.jdbc.connections.spi.AbstractDataSourceBasedMultiTenantConnectionProviderImpl;
import org.springframework.beans.factory.annotation.Value;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import br.com.ghnetsoft.comprasfood.sistemaapi.dto.RedeDTO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TenantConnectionProvider extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {

    private static final long serialVersionUID = 3927415181023159263L;
    @Value("${spring.datasource.driverClassName}")
    private String datasourceDriver;
    @Value("${sql.server.usuario}")
    private String sqlServerUsuario;
    @Value("${sql.server.senha}")
    private String sqlServerSenha;
    @Value("${sql.server.porta}")
    private String sqlServerPorta;
    @Value("${sql.server.url}")
    private String sqlServerUrl;
    private transient Map<String, DataSource> dataSources = new HashMap<>();

    @Override
    protected DataSource selectAnyDataSource() {
        log.info("selectAnyDataSource");
        return dataSources.values().iterator().next();
    }

    @Override
    protected DataSource selectDataSource(final String tenantId) {
        log.info("selectDataSource");
        log.info("tenantId: " + tenantId);
        DataSource ds = dataSources.get(tenantId);
        if (ds != null) {
            return ds;
        }
        final RedeDTO rede = TenantContext.getRede();
        log.info("rede: " + rede.toString());
        final HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(datasourceDriver);
        if (rede.isBaseLocal()) {
            log.info("sqlServerUrl: " + sqlServerUrl);
            log.info("sqlServerPorta: " + sqlServerPorta);
            log.info("sqlServerUsuario: " + sqlServerUsuario);
            hikariConfig.setJdbcUrl("jdbc:sqlserver://" + sqlServerUrl + ";databaseName=" + rede.getNomeBanco());
            hikariConfig.setUsername(sqlServerUsuario);
            hikariConfig.setPassword(sqlServerSenha);
        } else {
            log.info("rede.getUrl(): " + rede.getUrl());
            log.info("rede.getUsuarioAcesso(): " + rede.getUsuarioAcesso());
            log.info("rede.getPorta(): " + rede.getPorta());
            hikariConfig.setJdbcUrl("jdbc:sqlserver://" + rede.getUrl() + ";databaseName=" + rede.getNomeBanco());
            hikariConfig.setUsername(rede.getUsuarioAcesso());
            hikariConfig.setPassword(rede.getSenhaAcesso());
        }
        // minimo de conexões prontas para cada banco de dados
        hikariConfig.setMinimumIdle(2);
        // maximo de conexões para cada banco de dados
        hikariConfig.setMaximumPoolSize(10);
        // tempo maximo para conectar no banco - em segundos
        hikariConfig.setConnectionTimeout(SECONDS.toMillis(5));
        // tempo maximo para conexão ociosa - em segundos
        // não permite menor que 10
        hikariConfig.setIdleTimeout(SECONDS.toMillis(10));
        // tempo maximo de vida da conexão - em segundos
        // não permite menor que 30
        hikariConfig.setMaxLifetime(SECONDS.toMillis(30));
        // query para testar se conexão está ativa
        hikariConfig.setConnectionTestQuery("SELECT 1");
        // tempo maximo para agurdar teste de conexão - em segundos
        // não permite menor que 0.25
        hikariConfig.setValidationTimeout(SECONDS.toMillis(1));
        ds = new HikariDataSource(hikariConfig);
        dataSources.put(tenantId, ds);
        return ds;
    }
}

TenantContext

package br.com.ghnetsoft.comprasfood.sistemaapi.service.tenan;

import br.com.ghnetsoft.comprasfood.sistemaapi.dto.RedeDTO;

public class TenantContext {

    private static final ThreadLocal<String> TENANT = new ThreadLocal<>();
    private static final ThreadLocal<RedeDTO> REDE = new ThreadLocal<>();

    public static void setTenantId(final String tenantId) {
        TENANT.set(tenantId);
    }

    public static String getTenantId() {
        return TENANT.get();
    }

    public static RedeDTO getRede() {
        return REDE.get();
    }

    public static void setRede(final RedeDTO rede) {
        REDE.set(rede);
    }

    public static void clear() {
        TENANT.remove();
        REDE.remove();
    }
}

TenantIdentifierResolver

package br.com.ghnetsoft.comprasfood.sistemaapi.service.tenan;

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;

public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {

    public static final String DEFAULT_TENANT_ID = "comprasfood_usuario";

    @Override
    public String resolveCurrentTenantIdentifier() {
        final String currentTenantId = TenantContext.getTenantId();
        if (currentTenantId != null) {
            return currentTenantId;
        }
        return DEFAULT_TENANT_ID;
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

Quando coloco este endereço http://localhost:8702/modulo-sistema-api/swagger-ui.html

Ele não mostra nada, porque ao debugar ele entra na classe TenantInterceptor no método processaRedePublico.

Como não tem valor no request.getHeader(“chave-rede”), ele retorna false e a tela fica em branco.

Como estou desenvolvendo uma api para que outros sistemas utilizarem e como uma parte da documentação é o swagger, como faço para liberar o swagger ?