From cafaa85652517eee7da6b6ee64b2c95621cca884 Mon Sep 17 00:00:00 2001 From: Maximillian Arruda Date: Fri, 11 Mar 2022 22:27:15 -0300 Subject: [PATCH] Adicionado gerador de hash e jwt builder --- pom.xml | 12 ++-- .../arrudalabs/mizudo/model/Membro.java | 7 +++ .../arrudalabs/mizudo/model/Usuario.java | 15 +++++ .../membros/UsuarioDoMembroResource.java | 57 +++++++++++++++++ .../mizudo/services/GeradorDeHash.java | 38 +++++++++++ .../mizudo/services/JwtTokenBuilder.java | 63 +++++++++++++++++++ src/main/resources/application.properties | 4 ++ .../membros/UsuarioDoMembroTest.java | 61 ++++++++++++++++++ .../mizudo/services/JwtTokenBuilderTest.java | 25 ++++++++ 9 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroResource.java create mode 100644 src/main/java/io/github/arrudalabs/mizudo/services/GeradorDeHash.java create mode 100644 src/main/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilder.java create mode 100644 src/test/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroTest.java create mode 100644 src/test/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilderTest.java diff --git a/pom.xml b/pom.xml index 784a1ec..84a14ee 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,6 @@ io.quarkus quarkus-smallrye-openapi - io.quarkus quarkus-hibernate-orm-rest-data-panache @@ -74,15 +73,20 @@ org.jboss.resteasy resteasy-links - + + io.quarkus + quarkus-smallrye-jwt-build + + + io.quarkus + quarkus-smallrye-jwt + com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.13.0 test - - io.quarkus quarkus-junit5 diff --git a/src/main/java/io/github/arrudalabs/mizudo/model/Membro.java b/src/main/java/io/github/arrudalabs/mizudo/model/Membro.java index d58c969..84b7fac 100644 --- a/src/main/java/io/github/arrudalabs/mizudo/model/Membro.java +++ b/src/main/java/io/github/arrudalabs/mizudo/model/Membro.java @@ -28,6 +28,11 @@ public static Optional buscarPorId(Long id) { return Membro.findByIdOptional(id); } + /** + * Método dedicado para testes + * Não utilizar em produção + */ + @Deprecated public static void removerTodosMembros() { List membros = Membro.listAll(); membros.stream().forEach(Membro::apagar); @@ -81,6 +86,8 @@ private void apagar() { this.emails.clear(); this.telefones.clear(); this.examesMedicos.clear(); + Usuario.buscarUsuariosDoMembro(this) + .forEach(Usuario::delete); this.persist(); this.delete(); } diff --git a/src/main/java/io/github/arrudalabs/mizudo/model/Usuario.java b/src/main/java/io/github/arrudalabs/mizudo/model/Usuario.java index a07fda0..5f85123 100644 --- a/src/main/java/io/github/arrudalabs/mizudo/model/Usuario.java +++ b/src/main/java/io/github/arrudalabs/mizudo/model/Usuario.java @@ -3,12 +3,27 @@ import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import javax.persistence.*; +import java.util.Map; import java.util.Set; +import java.util.stream.Stream; @Entity @Table(name = "usuarios") public class Usuario extends PanacheEntityBase { + public static Stream buscarUsuariosDoMembro(Membro membro) { + return Usuario.stream("membro.id = :membroId", Map.of("membroId", membro.id)); + } + + /** + * Método dedicado aos testes + * Não utilizar esse método em produção + */ + @Deprecated + public static void apagarTodosOsUsuarios() { + Usuario.deleteAll(); + } + @Id public String username; diff --git a/src/main/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroResource.java b/src/main/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroResource.java new file mode 100644 index 0000000..8014ea4 --- /dev/null +++ b/src/main/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroResource.java @@ -0,0 +1,57 @@ +package io.github.arrudalabs.mizudo.resources.membros; + +import io.github.arrudalabs.mizudo.model.Membro; +import io.github.arrudalabs.mizudo.model.Usuario; +import io.github.arrudalabs.mizudo.validation.DeveSerIdValido; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +@Path("/membros/{membroId}/user") +public class UsuarioDoMembroResource { + + @PUT + public UsuarioRegistrado definirUsuario( + @DeveSerIdValido( + entityClass = Membro.class, + message = "Membro inválido" + ) + @PathParam("membroId") final Long membroId, + @Valid final NovoUsuario novoUsuario) { + + return UsuarioRegistrado.of(novoUsuario.definirUsuario(membroId)); + } + + public static class NovoUsuario { + + @NotBlank + public String username; + @NotBlank + public String senha; + @NotBlank + public String confirmacaoSenha; + + public Usuario definirUsuario(Long membroId) { + var usuario = new Usuario(); + usuario.username = this.username; + //TODO implementar + return usuario; + } + } + + public static class UsuarioRegistrado { + + public static UsuarioRegistrado of(Usuario usuario) { + var usuarioRegistrado = new UsuarioRegistrado(); + usuarioRegistrado.username = usuario.username; + return usuarioRegistrado; + } + + public String username; + public String senha = "*****"; + } + +} diff --git a/src/main/java/io/github/arrudalabs/mizudo/services/GeradorDeHash.java b/src/main/java/io/github/arrudalabs/mizudo/services/GeradorDeHash.java new file mode 100644 index 0000000..8706e74 --- /dev/null +++ b/src/main/java/io/github/arrudalabs/mizudo/services/GeradorDeHash.java @@ -0,0 +1,38 @@ +package io.github.arrudalabs.mizudo.services; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.enterprise.context.ApplicationScoped; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +@ApplicationScoped +public class GeradorDeHash { + + @ConfigProperty(name = "gerador.senha.algoritmo",defaultValue = "PBKDF2WithHmacSHA512") + private String algoritmo; + @ConfigProperty(name = "gerador.senha.iteracoes",defaultValue = "150000") + private Integer iteracoes; + @ConfigProperty(name = "gerador.senha.tamanho.chave",defaultValue = "32") + private Integer tamanhoChave; + + public String gerarHash(String salt, String senha){ + try { + var hash = SecretKeyFactory.getInstance(this.algoritmo) + .generateSecret( + new PBEKeySpec( + senha.toCharArray(), + salt.getBytes(StandardCharsets.UTF_8), + this.iteracoes, + this.tamanhoChave + ) + ).getEncoded(); + return Base64.getEncoder().encodeToString(hash); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilder.java b/src/main/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilder.java new file mode 100644 index 0000000..087ae38 --- /dev/null +++ b/src/main/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilder.java @@ -0,0 +1,63 @@ +package io.github.arrudalabs.mizudo.services; + +import io.smallrye.jwt.build.Jwt; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.enterprise.context.ApplicationScoped; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@ApplicationScoped +public class JwtTokenBuilder { + + private final Long duracaoEmMinutos; + private final String privateKey; + private final String issuer; + private final String keyId; + + public JwtTokenBuilder( + @ConfigProperty(name = "jwt.token.duracao.minutos", defaultValue = "300") final Long duracaoEmMinutos, + @ConfigProperty(name = "jwt.private.key") final String privateKey, + @ConfigProperty(name = "jwt.issuer") final String issuer + ) { + this.duracaoEmMinutos = duracaoEmMinutos; + this.privateKey = privateKey; + this.issuer = issuer; + this.keyId = UUID.randomUUID().toString(); + } + + public String gerarToken(String username, + Set papeis) throws NoSuchAlgorithmException, InvalidKeySpecException { + + long currentTimeInSecs = System.currentTimeMillis() / 1000; + var claimsBuilder = Jwt.claims(); + PrivateKey privateKey = decodePrivateKey(); + + claimsBuilder.issuer(this.issuer); + claimsBuilder.subject(username); + claimsBuilder.issuedAt(currentTimeInSecs); + claimsBuilder.expiresAt(tempoDeExpiracao(currentTimeInSecs)); + claimsBuilder.groups(papeis); + + return claimsBuilder.jws().keyId(this.keyId).sign(privateKey); + } + + private PrivateKey decodePrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + var decodedKey = Base64.getDecoder().decode(this.privateKey); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); + KeyFactory keyFactory=KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(keySpec); + } + + public long tempoDeExpiracao(long currentTimeInSecs) { + return currentTimeInSecs + (this.duracaoEmMinutos * 60); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3b7c4fb..0d9e762 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,6 +6,10 @@ quarkus.datasource.username=username-default quarkus.datasource.jdbc.url=jdbc:h2:mem:default quarkus.datasource.jdbc.max-size=13 +jwt.issuer=https://mizudo-app.com +jwt.private.key=MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXrLzooX0pXJ7N13Z9A9FP1TdW2IWsrCWgPzwi1jiTTj/DRw4jVHvfEuts1P6mp/XwrlWdqpq54GsFO7x2/KqjgmbOnbzWJQNSBfnu+NZV2M3SLu8oUwEBOL2+qosix8EzaG7s8JruMdkfzHnuizpkiVigDckOL5LNGuElyvElf436c0RcN4dGOcHaZHJEOs9s0Ep5TFz0xfEJaw/PxItam+Uxl69olWJXUKd5dgMwyPw9txgXvQX7rjkCc9N6SeW+vCErgN0G5HeF/H+D2f0Mnpg0S3cFlyQbomJ05XsFC2o0m8znGtct0cYDCjsF4Acqyk/B1GytkH0io5y6PUU7AgMBAAECggEAMxUCKnb46E1BTqIaTdBTVkfA84bIvnbb1TR21GTBeRsJboMnx85USbTdhTHW8dnvgre0Lf2fTqq+YGzV9Fz0O01Xwo5tYtHkZGOO4WT9CryR1Q/pi/Moi3jIrsZRRc1wzBeIBFSkWUSAk60SA7zwVZvMNVtvM0/iaSF8YnYO8UmXkARbWpfypAgiW7sk3BRWavpYrjQ9tygjHeWi9nxmuL/yZUT9+ojEvdFsedOG1oofEe7a4jz2ftvJTJlMS4IpEBql5OxhrAAkjHyu0AXaiPnhzXezdoZNlAqXXrG9XfklAgoxbKSfucZaR2Fk35vsaY1FW2A40a1ojqUTXrqgwQKBgQDIYDyUW/1JFEVJ7VWv9j4ZutKRUGoSUeDnRUCjkwSyQD/9D5gW/DduofIIkEr0d45m6Dpr8/1ke8joutgw35WJepFGjCbV+VKM5gYwxPElIVuZHOqU5pVAREtgslEJGEeJSUFt/XBIDnh8SOSERZoxp2rD1+6Je2l55lVb1FkcvQKBgQDBx4qHL8oAoYu692LRQJylX2bPDRVfMhcd7IT5IiB40qZg4Zx2ATZXGZnim5kYcTb+CzBArekZJpujgWJgBSZACF6PSME5J1gCyeM/eVuXcgfEFg/eheNQvwcT1rOqzoilUCEUskt+iDh8sZF7ugv7Hwz6gjCgFrU6xKM5Jw0VVwKBgAm8IzVSAsw6H+OnlDIoVHwLh49nyL97GuIhizJnQiMm2/T/fPQg7NCDLa8c04dtB44YWj5EbFslaG1mJnq5Yhp7yfMTfA+JPWl7A3H7H3weUsTtkrgJO9cTelYIU8eNVmxlEri1R284xMHFCNUkTIl9CMLbCsvDIyy49AyyFGJBAoGAJ2rCh/s1mOlDdhWqWSxOcOk2DXn41fNndmPNLKw/dLRMubpKzEo01oTTo5/JBYrKB+UYvJ9jogUTda/05dlGTZImQfknrMuPAIo2movCEM1WPRHLypTrh1pEQ0nSJDV84Durbv1Rk/x7mQm5sTRICZ3oUvC2mWnlFX44QYDrDWMCgYBUsqFh5MymEd+fky0vtpqcn+2HHlnxWpx7oTs3AVQNX4UDaKTSOcXAweiBjwLpgfhijdR/dph/92C8uL0q6SEel7FR06XlqDUzbG2p4IOZt4SVuXkCuYhFcl6xzAj1s/+ewnaCc4ETolWoJw3/3brbQGyT8PeK7BZqVHdMAv3iUA== +jwt.token.duracao.minutos=1 + %dev.quarkus.datasource.db-kind=h2 %dev.quarkus.datasource.username=username-default %dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:default diff --git a/src/test/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroTest.java b/src/test/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroTest.java new file mode 100644 index 0000000..e2817f7 --- /dev/null +++ b/src/test/java/io/github/arrudalabs/mizudo/resources/membros/UsuarioDoMembroTest.java @@ -0,0 +1,61 @@ +package io.github.arrudalabs.mizudo.resources.membros; + +import io.github.arrudalabs.mizudo.model.Membro; +import io.github.arrudalabs.mizudo.model.Usuario; +import io.github.arrudalabs.mizudo.resources.ApiTestSupport; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import javax.json.Json; +import javax.ws.rs.core.Response; + +import java.util.Map; +import java.util.UUID; + +import static org.hamcrest.Matchers.*; +import static org.hamcrest.MatcherAssert.*; + +@QuarkusTest +public class UsuarioDoMembroTest { + + @Inject + ApiTestSupport apiTestSupport; + + @BeforeEach + @AfterEach + void limparMembros(){ + apiTestSupport.execute(Usuario::apagarTodosOsUsuarios); + apiTestSupport.execute(Membro::removerTodosMembros); + } + + @Test + void deveDefinirUsuarioParaUmMembroValido(){ + + var membro = apiTestSupport.executeAndGet(()->Membro.novoMembro(UUID.randomUUID().toString())); + + String senha = UUID.randomUUID().toString(); + String username = UUID.randomUUID().toString(); + apiTestSupport + .newAuthenticatedRequest() + .log().everything() + .contentType(ContentType.JSON) + .body(Json.createObjectBuilder() + .add("username", username) + .add("senha", senha) + .add("confirmacaoSenha",senha) + .build().toString()) + .put("/resources/membros/{id}/user", Map.of("id",membro.id)) + .then() + .log().everything() + .statusCode(Response.Status.OK.getStatusCode()) + .body("username", is(username)) + .body("senha", is("*****")); + + } + + +} diff --git a/src/test/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilderTest.java b/src/test/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilderTest.java new file mode 100644 index 0000000..5414db6 --- /dev/null +++ b/src/test/java/io/github/arrudalabs/mizudo/services/JwtTokenBuilderTest.java @@ -0,0 +1,25 @@ +package io.github.arrudalabs.mizudo.services; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Set; + +@QuarkusTest +class JwtTokenBuilderTest { + + @Inject + JwtTokenBuilder jwtTokenBuilder; + + @Test + void deveGerarUmTokenValido() throws NoSuchAlgorithmException, InvalidKeySpecException { + + System.out.println(jwtTokenBuilder.gerarToken("admin", Set.of("ADMIN", "USER"))); + + } + + +} \ No newline at end of file