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 ?