Configurando RestTemplate para fazer requisições SSL (HTTPS)
É recorrente a necessidade de realizarmos requisições a aplicações de terceiros que utilizem o HTTPS para criptografar as informações que irão trafegar na rede.
Neste exemplo, iremos mostrar como configurar o suporte SSL (HTTPS) para uma aplicação SpringBoot que utiliza o Resttemplate como meio de comunicação.
Criação do certificado
Iremos utilizar o JDK Keytool para gerar o certificado no formato PKCS12.
O comando a seguir, criará um certificado de nome devrodrigues.p12 com a validade de 365 dias dentro do diretório corrente.
keytool -genkeypair -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore devrodrigues.p12 -validity 365
O comando gera a uma interação no shell, preencha com os valores mais adequados a cada pergunta.
Enter keystore password: 123456
Re-enter new password: 123456
What is your first and last name?
[Unknown]: DevRodrigues
What is the name of your organizational unit?
[Unknown]: DevRodrigues
What is the name of your organization?
[Unknown]: DevRodrigues
What is the name of your City or Locality?
[Unknown]: Brazil
What is the name of your State or Province?
[Unknown]: SaoPaulo
What is the two-letter country code for this unit?
[Unknown]: SP
Is CN=DevRodrigues, OU=DevRodrigues, O=DevRodrigues, L=Brazil, ST=SaoPaulo, C=SP correct?
[no]: yes
Ao finalizar essa operação o certificado devrodrigues.p12 é criado no diretório atual.
Configuração da aplicação em SpringBoot
Adicione o certificado na sua aplicação
Copei o certificado gerado na etapa anterior e cole dentro do diretório resources da sua aplicação.
Adicione as seguintes dependências no seu projeto
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
Configure as propriedades da aplicação
Adicione as seguintes configurações no arquivo application.properties.
# SSL
server.ssl.key-store=classpath:devrodrigues.p12
server.ssl.key-store-password=123456
server.ssl.keyStoreType=PKCS12
server.port=8443
#RESTTEMPLATE
resttemplate.readTimeout=150000
resttemplate.connectionTimeout=150000
resttemplate.password=123456
resttemplate.protocol=TLS
Encapsulando as properties num objeto Java
Essa classe tem como objetivo, encapsular as properties relacionadas a configuração do RestTemplate, já as configurações do certificado vamos injetar utilizando a anotação
, assim você acaba conhecendo formas diferentes de injetar o valor de uma property num objeto Java.@Value
@Component
@ConfigurationProperties("resttemplate")
public class ResttemplateProperties {
private Integer readTimeout;
private Integer connectionTimeout;
private String password;
private String protocol;
public ResttemplateProperties() {
}
public Integer getReadTimeout() {
return readTimeout;
}
public Integer getConnectionTimeout() {
return connectionTimeout;
}
public String getPassword() {
return getPassword();
}
public String getProtocol() {
return protocol;
}
}
Configurando o Bean do RestTemplate
Agora iremos adicionar o nosso certificado ao Resttemplate:
import org.springframework.beans.factory.annotation.Value;@Configuration
public class ResttemplateConfiguration {
@Value("{server.ssl.key-store}")
private String keyStoreFile;
@Value("{server.ssl.keyStoreType}")
private String keyStoreType;
private final ResttemplateProperties properties;
public ResttemplateConfiguration(ResttemplateProperties properties) {
this.properties = properties;
}
@Bean
public RestTemplate restTemplate() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
KeyStore clientStore = KeyStore.getInstance(keyStoreType);
File file = ResourceUtils.getFile(keyStoreFile);
clientStore.load(new FileInputStream(file), properties.getPassword().toCharArray());
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
sslContextBuilder.setProtocol(properties.getProtocol());
sslContextBuilder.loadKeyMaterial(clientStore, properties.getPassword().toCharArray());
sslContextBuilder.loadTrustMaterial(new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build());
CloseableHttpClient httpClient = HttpClients
.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
requestFactory.setConnectTimeout(properties.getConnectionTimeout());
requestFactory.setReadTimeout(properties.getReadTimeout());
return new RestTemplate(requestFactory);
}
}
Prontinho! Com os passos acimas executados nossa aplicação já está configurada para realizar chamadas a endpoints seguros.
Testando nossa implementação
Para o primeiro teste vamos rodar a aplicação e acessar o seguinte endereço no navegador https://localhost:8443 e visualizar as informações do certificado conforme imagem a seguir.
No segundo teste vamos fazer uma requisição a um API publica do Senado utilizando o Bean do RestTemplate criado nas etapas anteriores.
@Service
public class RequestGateway {
private final RestTemplate restTemplate;
public RequestGateway(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public void mustPerformHttpsCall() {
executeRequest(() -> {
var url = "https://legis.senado.leg.br/dadosabertos/materia/assuntos?indAtivos=S";
var uri = convertToUri(url);
ResponseEntity<String> response = restTemplate.exchange(
uri,
HttpMethod.GET,
null,
String.class);
// resposta ignorada
});
}
private void executeRequest(Runnable runnable) {
try {
runnable.run();
} catch (HttpClientErrorException | HttpServerErrorException e) {
// trate o erro da forma que achar mais conveniente para sua aplicação
e.printStackTrace();
}
}
private String convertToUri(String url) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
return builder.toUriString();
}
}
Poderiamos utilizar um teste unitário para realizar uma chamado a classe acima. Mas resolvemos utilizar a implementação de CommandLineRunner que é uma interface funcional a qual é executada assim que o container do SpringBoot for inicializado.
@SpringBootApplication
public class HttpsspringbootApplication implements CommandLineRunner {
private final RequestGateway gateway;
public HttpsspringbootApplication(RequestGateway gateway) {
this.gateway = gateway;
}
public static void main(String[] args) {
SpringApplication.run(HttpsspringbootApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
gateway.mustPerformHttpsCall();
}
}
Por fim, o código fonte dessa aplicação esta no repositório hospedado no GitHub.