robsoncalixto / study-java

Repositório criado para estudar a linguagem java e seus detalhes.
MIT License
0 stars 0 forks source link

How to Do #7

Open dfpinto opened 4 years ago

dfpinto commented 4 years ago

Usando banco de dados H2

1) Inclua no pom.xml:

<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>

2) Inclua em application.properties:

datasource

spring.datasource.driver-class-name=org.h2.Driver

spring.datasource.url=jdbc:h2:file:~/test

spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.username=sa spring.datasource.password=

h2

spring.h2.console.enabled=true spring.h2.console.path=/h2-console

jpa

spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.hibernate.ddl-auto=none spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

3) Acessar: localhost:8080/h2-console

dfpinto commented 4 years ago

Criando projeto via maven project

No STS: File / New / Maven Project Marque o checkbox "Create a simple project..." Preencha os campos: Group Id: nome do pacote, exemplo br.com.studyjava Artifect Id: nome do projeto, exemplo javanapratica Finish

dfpinto commented 4 years ago

Criando projeto a partir do site do spring

1) Entre na página https://start.spring.io/ 2) Marque as opções: Project: Maven Language: Java Spring Boot: 2.3.1 3) Preencha os campos: Group: br.com.studyjava Artifact: javanapratica Packaging: jar Java: 8 4) Escolha as dependências: WEB, JPA, DEV TOOLS, Lombok, Spring Security, H2, MySql Driver 5) Clique no botão GENERATE 6) Descompacte o zip gerado em algum lugar do seu computador 7) No STS, clique em File / Import / Maven / Existing Maven Projects

dfpinto commented 4 years ago

Principais atalhos do STS

1) ctrl + shift + F : formatar o texto 2) ctrl + D : deletar uma linha 3) ctrl + shift + O : importar dependências 4) ctrl + alt + up ou down : duplica a linha selecionada 5) sysout + ctrl + space : escreve System.out.println(); 6) main + ctrl + space: escreve public static void main(String[] args) {} 7) ctrl + 1 : declara uma variável do tipo informado Exemplo: new ArrayList<>(); [pressione ctrl + 1 em cima da instrução] A IDE dará a opção "Assign statement to new local variable". Selecione-a. 8) ctrl + space : autopreenchimento ou inclusão de alguma método em uma classe 9) ctrl + shift + / : comentário de bloco 10) ctrl + shift + \ : retira comentário de bloco 11) log + ctrl + space : escreve "private static final Logger log = LoggerFactory.getLogger(RestWebClientController.class);"

dfpinto commented 4 years ago

Configurando banco de dados MySql na raça (persistence.xml)

1) Inclua as dependências Maven a serem baixadas:

<dependencies>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.12.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.12.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
</dependencies>

2) Configure o JPA no seu projeto por meio do arquivo persistence.xml Crie uma pasta "META-INF" a partir da pasta "resources" Dentro da pasta META-INF crie um arquivo "persistence.xml" Conteúdo do arquivo persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
  <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
                        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
     version="2.1">

       #name = Apelido desejado; transaction-type = RESOURCE_LOCAL = significa que o controle da transação será manual;
       <persistence-unit name="javanapratica-jpa" transaction-type="RESOURCE_LOCAL">
           <properties>
               #url do banco de dados desejado; neste caso é o mysql que está em localhost. Se o banco estivesse em um servidor usaríamos por exemplo jdbc:mysql://108.22.33.33/aulajpa?useSSL=false&amp;serverTimezone=UTC 
               #dbjavanapratica é o nome do database criado no mysql
               <property name="javax.persistence.jdbc.url"
                         value="jdbc:mysql://localhost/dbjavanapratica?useSSL=false&amp;serverTimezone=UTC" />
               <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
               <property name="javax.persistence.jdbc.user" value="root" />
               <property name="javax.persistence.jdbc.password" value="" />
               <property name="hibernate.hbm2ddl.auto" value="update" />

               <!-- https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/dialect/package-summary.html -->
               <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect" />
           </properties>
      </persistence-unit>
</persistence>

nivelamento-sobre-jpa.pdf

dfpinto commented 4 years ago

Configurando banco de dados MySql via classe e intercambiando profiles

Usando application.properties, application-dev.properties, application-homo.properties e application-prod.properties.

  1. Servidor MySql Se vamos usar o banco de dados mysql será necessário um servidor. A nível de estudo sugiro usar o Xampp. Ele já vem com apache e mysql e é muito fácil usá-lo. Quando estiver executando seu projeto java inicie apenas o MySql no Xampp. Não há necessidade de iniciar o Apache, já que o Spring sobe o Tomcat por padrão.

  2. Configurações do banco As configurações do banco de dados, como string de conexão, usuário e senha estarão em um profile do Spring chamados properties. Para configurar o nosso banco mysql usaremos o arquivo application-dev.properties

    #datasource
    spring.datasource.driver-class-name=com.mysql.jdbc.driver
    spring.datasource.url=jdbc:mysql://localhost:3306/dbjwt?useSSL=false&serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=
    #jpa
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.format_sql=true
    spring.jpa.hibernate.ddl-auto=none
    spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
  3. Profile de desenvolvimento, teste e produção. Todo projeto teremos os ambientes de dev, homo e prod. Para separá-los usamos profiles, que nada mais são que os arquivos application.properties. Então para o ambiente de dev, podemos chamar de application-dev.properties, de homo application-homo.properties e produção application-prod.properties. Tudo que estiver em applicatin.properties será comum entre os outros. E como o Spring vai saber qual usar? Muito simples. Podemos usar o arquivo application.properties para isso, incluiremos a linha spring.profiles.active=dev.

  4. Criando configurações específicas por profile Crio uma classe e a anoto com @Configuration e @Profile("dev"). Dessa forma todos os Beans que estiverem contidos nessa classe estarão disponíveis para uso, caso estejamos usando o profile "dev" em application.properties. Então, podemos chamar a classe de DevConfig e incluir, por exemplo, massa inicial de teste.

dfpinto commented 4 years ago

Criando massa de teste inicial

Crie um arquivo chamado data.sql com as instruções de insert das tabelas que irá usar e ponha em src/main/resources Exemplo: INSERT INTO USUARIO(nome, email, senha) VALUES('DIRLEY','dirley.figueredo@gmail.com','dirley');

dfpinto commented 4 years ago

Intercambiando os properties

dfpinto commented 4 years ago

Como instalar e usar o Lombok em projetos Java

https://receitasdecodigo.com.br/java/como-usar-o-lombok-em-projetos-java#:~:text=O%20Lombok%20%C3%A9%20um%20framework,padr%C3%A3o%20builder%20e%20muito%20mais.

dfpinto commented 4 years ago

How to parse JSON in Java

https://stackoverflow.com/questions/2591098/how-to-parse-json-in-java

dfpinto commented 4 years ago

Consumindo serviço usando RestTemplate, URLConnection e WebClient (não bloqueante)

Veja a implementação em https://github.com/robsoncalixto/study-java/blob/master/ConsumindoServicoNaWebDeTresFormasDiferentes.zip.

O video abaixo explica em detalhes como implementar um WebClient. https://www.youtube.com/watch?v=Q1BjCuAQRrQ

Necessário adicionar as dependências abaixo:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectreactor</groupId>
            <artifactId>reactor-spring</artifactId>
            <version>1.0.1.RELEASE</version>
        </dependency>
dfpinto commented 4 years ago

Como implementar Spring Security e JWT de forma fácil.

1. Inclua a dependência abaixo no pom.xml. O simples fato de incluirmos essa dependência, o spring bloqueará todos os acessos aos endpoints, pois esse é o padrão. Se você tentar acessar alguma uri, tomará o erro 401 unauthorized.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2. Crie uma classe que conterá as configurações de segurança. Nada melhor que chamá-la de SecucityConfigurations. Precisamos, então, dizer pro spring que essa classe é de configuração e o que nela constar será lido e carregado pelo spring quando a aplicação iniciar. Para isso usamos a anotação @Configuration. Também precisamos habilitar o modo de segurança e pra isso usamos a anotação @EnableWebSecutiry. Por fim, estendemos nossa classe de WebSecurityConfigurerAdapter que nos fornecerá alguns métodos contendo comportamento padrão que iremos sobrescrever, adequando-os ao nosso contexto.

package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{
...
}

2.1. Sobrescreva os métodos "configure". Ele é sobrecarregado três vezes e cada um tem uma responsabilidade distinta.

package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{

   // Configurações de autenticação
   @Override
   public void configure(AuthenticationManagerBuilder auth) throws Exception {
   }

   // Configurações de autorização / acesso
   // Será nesse método que irei definir quais URIs (requests) terão acesso: o que será público, privado, qual método pode ser executado.
   @Override
   public void configure(HttpSecurity http) throws Exception {
   }

   // Configurações de recursos estáticos (imagens, css, js, etc)
   @Override
   public void configure(WebSecurity web) throws Exception {
   }
}

2.2. Dando acesso a uma URI pública Consideremos que qualquer usuário poderá consultar todos os tópicos ou um específico.

package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{

   // Configurações de autenticação
   @Override
   public void configure(AuthenticationManagerBuilder auth) throws Exception {
   }

   // Configurações de autorização / acesso
   @Override
   public void configure(HttpSecurity http) throws Exception {
       // Consultando todos os tópicos com /topicos
       // Consultando um tópico específico com /topicos/1
       http.authorizeRequest()
              .antMatchers(HttpMethod.GET, "/topicos").permitAll()
              .antMatchers(HttpMethod.GET, "/topicos/*").permitAll();
   }

   // Configurações de recursos estáticos (imagens, css, js, etc)
   @Override
   public void configure(WebSecurity web) throws Exception {
   }
}

3. Restringindo o acesso a uma URI privada exigindo autenticação Para implementar uma autenticação é necessário login e senha do usuário e para isso criaremos uma classe chamada, adivinhe? Usuario. Então, teremos uma classe de domínio chamada Usuario, anotada como uma Entity. Porém o spring security precisa saber quem é a classe que conterá as credenciais de acesso e neste artigo vou explicar como fazê-lo implementando a interface UserDetails. Ao implementar essa interface obrigatoriamente teremos que cumprir o contrato que ela estabelece. Sabemos também que existem usuários ou atores de toda ordem: gestores, operadores, clientes, coordenadores, consultores etc. Para essa coleção de tipos de usuário damos o nome de perfis. E assim como criamos a classe Usuario implementando a interface UserDetails para que o spring possa reconhecer quem identifica as credenciais de um usuário, também faremos uma classe Perfil herdando da interface GrantedAuthority para que o spring reconheça quais são os perfis. Por fim, repare no método chamado formLogin() que usei na classe de configuração abaixo. Esse método fará com que o spring gere uma tela de login para nós. Entretanto, nós ainda não ensinamos ao spring o que fazer com o login e senha dessa tela. Vamos aprender a fazer isso no próximo item.

Precisamos implementar os métodos da interface UserDetails: // Lista ou coleção dos perfis do usuário. public Collection<? extends GrantedAuthority> getAuthorities() return this.perfis;

// Para que o spring saiba recuperar a senha de seu usuário passamos o atributo que a identifica. public String getPassword() return this.senha;

// Para que o spring saiba recuperar o username de seu usuário passamos o atributo que o identifica. public String getUsername() return this.email;

// Caso queira controlar expiração de contra, implemente este método, senão passe true. public boolean isAccountNonExpired() return true;

// Caso queira controlar bloqueio de contra, implemente este método, senão passe true. public boolean isAccountNonLocked() return true;

// Caso queira controlar expiração das credenciais, implemente este método, senão passe true. public boolean isCredentialsNonExpired() return true;

// Caso queira controlar se a contra está ou não habilitada, implemente este método, senão passe true. public boolean isEnabled() return true;

Precisamos implementar o método da interface GrantedAuthority: public String getAuthority() return this.nome;

package br.com.studyjava.models;

@Entity
public class Perfil implements GrantedAuthority {
    private Long id;
    private String nome;
     ...
    @Override
    public String getAuthority() {
        return this.nome;
    }
    ...
}

package br.com.studyjava.models;

@Entity
public class Usuario implements UserDetails {
    private Long id;
    private String senha;
    private String email;
    private String nome;

    // Toda relação ToMany é Lazy e ao carregar o Usuário a JPA não carregará os perfis. Por isso precisamos dizer para a JPA que
    // queremos que ela traga os perfis e por esta razão usamos o EAGER.
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "perfil_usuario",
                   joinColumns = @JoinColumn(name = "usuario_id"),
               inverseJoinColumns = @JoinColumn(name = "perfil_id"))
    private List<Perfil> perfis = new ArrayList<>();
     ...
    @Override
    public String getPassword() {
        return this.senha;
    }

    @Override
    public String getUsername() {
        return this.email;
    }
    ...
}

package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{

   // Configurações de autenticação
   @Override
   public void configure(AuthenticationManagerBuilder auth) throws Exception {
   }

   // Configurações de autorização / acesso
   @Override
   public void configure(HttpSecurity http) throws Exception {
       // Consultando todos os tópicos com /topicos
       // Consultando um tópico específico com /topicos/1
       // Qualquer outro request precisa de autenticação do usuário.
       // Dizer para o spring montar uma tela de login.
       http.authorizeRequest()
              .antMatchers(HttpMethod.GET, "/topicos").permitAll()
              .antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
              .anyRequest().authenticated()
              .and().formLogin()
       ;
   }

   // Configurações de recursos estáticos (imagens, css, js, etc)
   @Override
   public void configure(WebSecurity web) throws Exception {
   }
}

4. Autenticando o login do usuário No item anterior nós pedimos ao autenticador de acesso que apresentasse a tela de login em nossa aplicação. Essa tela é apresentada quando tentamos acessar uma URI não autorizada ou não pública. Agora iremos dizer para o spring como autenticar o login do usuário e pra isso iremos implementar o método configure(AuthenticationManagerBuilder auth). A classe AuthenticationManagerBuilder possui um método chamado userDetailService que recebe como parâmetro uma classe de serviço que implementa a lógica de autenticação que desejamos. Para isso, então, criaremos a nossa service e chamaremos de AutenticacaoService. E para que o spring security saiba que essa classe é a que contém a lógica de autenticação é necessário que implementemos a interface UserDetailService. Essa classe vai dispor o método loadUserByUsername que retorna um UserDetail e que iremos implementar buscando e verificando se o username existe na base de dados. Repare abaixo que o método loadUserByUsername recebe apenas o parâmetro username do login. E a senha? A senha pertence a classe Usuario que implementa a interface UserDetail que possui o método getPassword. Esse método será acionado pelo Spring através do processo de autenticação. Se não lembra, dê uma olhadinha no item 3. Para verificarmos se o usuário existe na base criaremos um repository, pois não é de responsabilidade do service. Então criamos a interface UsuarioRepository estendendo a classe JPARepository e usaremos o método findByEmail. ...

package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{

   // Configurações de autenticação
   @Autowired
   private AutenticacaoService autenticacaoService;

   @Override
   public void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailService(autenticacaoService);
   }

...

public class AutenticacaoService implementes UserDetailService {
    @Autowired
    private UsuarioRepository usuRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         Optional<Usuario> usuario = usuRepo.findByEmail(username);
         if(usuario.isPresent()){
             return usuario.get();
         }

        throw new UsernameNotFoundException("Dados inválidos.");
    }
}

...

public interface UsuarioRepository extends JPARepository<Usuario, Long> {
     Usuario findByEmail(String email);
}

... 4.1 Criptografando a Senha Assim como o usuário, a senha também fica gravada no banco de dados, geralmente na tabela Usuario. Imagine, então, o login: user: rodrigo@gmail.com password: Rodrigo@2020 Qualquer um que conheça o mínimo de segurança sabe que a senha não pode ser gravada sem que seja submetida a alguma a um algorítimo de hash. O Spring fornece, através do método passwordEncoder, recurso para inputarmos nossa função de hash, porém o próprio spring security dispõe de uma classe para isso chamada BCryptPasswordEncoder. Então, veremos abaixo como usá-la em nosso login.

...

   @Override
   public void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailService(autenticacaoService).passwordEncoder(new BCryptPasswordEncoder());
   }

...

Mas como iremos criptografar a senha que está gravada no banco como Rodrigo@2020? Claro que podemos fazer isso no método setPassword da classe Usuario, porém, a nível de teste, podemos simplesmente criar uma classe main chamando o método encode da classe BCryptPasswordEncoder.

public static void main(String[] args){
    System.out.println(new BCryptPasswordEncoder().encoder("Rodrigo@2020"));
}
dfpinto commented 4 years ago

Autenticação via Token

Por que autenticar via token? Por padrão o Spring Security armazena, no lado do servidor, sessões em memória para cada usuário logado, identificando-os de forma única, contendo suas informações de autenticação e devolvendo para o cliente / navegador, através de cookie. Por outro lado sabemos que uma das características do modelo Rest é ser stateless, ou seja, não armazena nada entre requisição e resposta. E por que isso é ruim? Porque se eu tiver zilhões de usuários ocuparão muita memória, porque ao escalar minha aplicação não será possível compartilhar essas sessões, porque se o servidor cair perderei essas sessões.

Usando token 1. Primeira coisa é baixar a dependência.

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
</dependency>

2. Dizer para o spring security que não mais usaremos sessão. Lembra que existem 3 métodos configure de nossa classe SecurityConfigurations? Faremos isso na classe configure(HttpSecurity http). Vamos remover .and().formLogin() e incluir: .and().csrf().disable() .sessonManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

Como usaremos token automaticamente nossa API estará livre desse ataque.

package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{
   ...
   // Configurações de autorização / acesso
   @Override
   public void configure(HttpSecurity http) throws Exception {
       // Consultando todos os tópicos com /topicos
       // Consultando um tópico específico com /topicos/1
       // Qualquer outro request precisa de autenticação do usuário.
       http.authorizeRequest()
              .antMatchers(HttpMethod.GET, "/topicos").permitAll()
              .antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
              .anyRequest().authenticated()
              //.and().formLogin()
              .and().csrf().disable()
              .sessonManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
       ;
   }
...

3. Autenticando o login Como removemos o método formLogin() perdemos não só a tela de login como o controlador dessa página gerenciado pelo spring. Quanto a tela, não é tarefa do backend. Essa tela será fornecida pelo frontend. Mas o controle para gerenciar o login nós teremos que criar e daremos o nome de AutenticacaoController, usando a URI /auth. Não esqueça que se temos uma nova URI pública, temos que incluí-la em nosso configurador de acesso. Para que possamos realizar a autenticação de forma programática no spring, precisaremos da classe AuthenticationManager. O problema é que, por algum motivo, essa classe não está disponível para ser injetada em nosso controller de autenticação. Porém, ela pode ser criada pelo método authenticationManager do nosso configurador de segurança SecurityConfigurations e é isso que faremos (dando ctrl + space e escolhendo esse método), incluindo @Bean para que possamos injetá-la. Agora que já temos em nosso controller a classe AuthenticationManager injetada, usaremos o método authenticate para autenticarmos as credenciais do usuário. O problema é que esse método recebe o tipo UsernamePasswordAuthenticationToken. Então, precisamos converter o e-mail e senha do usuário nesse tipo e faremos criando o método converter na classe LoginForm.

package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{
   ...
   @Override
   @Bean  
   protected AuthenticationManager authenticationManager() throws Exception {
         return super.authenticationManager();
   }

   // Configurações de autorização / acesso
   @Override
   public void configure(HttpSecurity http) throws Exception {
       // Consultando todos os tópicos com /topicos
       // Consultando um tópico específico com /topicos/1
       // Qualquer outro request precisa de autenticação do usuário.
       http.authorizeRequest()
              .antMatchers(HttpMethod.GET, "/topicos").permitAll()
              .antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
              .antMatchers(HttpMethod.POST, "/auth").permitAll()
              .anyRequest().authenticated()
              .and().csrf().disable()
              .sessonManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
       ;
   }
...
----------------------------------------------------------------------------------------------------------------------
package br.com.studyjava.controller;

@RestController
@RequestMapping("/auth")
public class AutenticacaoController {

     @Autowired
     private AuthenticationManager authManager; 

     @PostMapping
     public ResponseEntity<?> autenticar(@RequestBody @Valid LoginForm form) {
          UsernamePasswordAuthenticationToken dadosLogin = form.converter();

          try {
              Authentication authentication = authManager.authenticate(dadosLogin);
              return ResponseEntity.ok().build();
          } catch(AuthenticationException e) {
              return ResponseEntity.badRequest().build();
         }
     }
}
------------------------------------------------------------------------------------------------------------------------
package br.com.studyjava.controller;

public class LoginForm {
     private Sgring email;
     private String senha;

     // setter...

     public UsernamePasswordAuthenticationToken converter {
         return new UsernamePasswordAuthenticationToken(this.email, this.senha);
     }
}

4. Gerando o token Agora que já autenticamos as credenciais do usuário já podemos gerar o token. Faremos isso no próprio controller AutenticacaoController. Para isso vamos criar um service chamado TokenService com o método gerarToken passando a variável authentication. Ele vai acionar a API da biblioteca jjwt que importamos no pom chamada Jwts. Precisamos setar diversos parâmetros que mostro abaixo mas antes é importante que dois desse parâmetros, o tempo de expiração e a secret, fiquem no arquivo application.properties. Podemos usar o node.js para gerar nossa secret: node -e "console.log(require('crypto').randomBytes(256).toString('base64'));" Exemplo:

# jwt
studyjava.jwt.secret=rm'!@N=Ke!~p8VTA2ZRK~nMDQX5Uvm!m'D&]{@Vr?G;2?XhbC:Qa#9#eMLN\}x3?JR3.2zr~v)gYF^8\:8>:XfB:Ww75N/emt9Yj[bQMNCWwW\J?N,nvH.<2\.r~w]*e~vgak)X"v8H`MH/7"2E`,^k@n<vE-wD3g9JWPy;CrY*.Kd2_D])=><D?YhBaSua5hW%{2]_FVXzb9`8FH^b[X3jzVER&:jw2<=c38=>L/zBq`}C6tT*cCSVC^c]-L}&/
studyjava.jwt.expiration=86400000

Explicando cada parâmetro do Jwts:

Jwts.builder()
  // Quem fez a geração do token?
 .setIssuer("API da equipe studyjava") 

 // Quem é o usuário autenticado que esse token pertence (id único)?
 .setSubject(logado.getId().toString()) 

 // Qual data o token foi gerado?
 .setIssuedAt(hoje)  

 // Qual o tempo de expiração do token? Definir o tempo de expiração no arquivo application.properties.
 .setExpiration(dataExpiracao) 

 // Qual o algoritmo a ser usado para criptografia o token e a chave secreta a ser usada? 
 // Definir o tempo de expiração e a secrect no arquivo application.properties.
 .signWith(SignaturedAlgorithm.HS256, secret) 

 // Compacta e gera o token.
 .compact()  
;

Fonte java:

@RestController
@RequestMapping("/auth")
public class AutenticacaoController {

     @Autowired
     private AuthenticationManager authManager; 

     @Autowired
     private TokenService tokenService; 

     @PostMapping
     public ResponseEntity<?> autenticar(@RequestBody @Valid LoginForm form) {
          UsernamePasswordAuthenticationToken dadosLogin = form.converter();

          try {
              Authentication authentication = authManager.authenticate(dadosLogin);
              String token = tokenService.gerarToken(authentication);
              System.out.println(token);
              return ResponseEntity.ok().build();
          } catch(AuthenticationException e) {
              return ResponseEntity.badRequest().build();
         }
     }
}

------------------------------------------------------------------------------------------------------------
@Service
public class TokenService {
     @Value("${studyjava.jwt.expiration}")
     private String expiration;

     @Value("${studyjava.jwt.secrect}")
     private String secret;

     public String token gerarToken(Authentication authentication) {
          Usuario logado = (Usuario)authentication.getPrincipal();
          Date hoje = new Date();
          Date dataExpiracao = new Date(hoje.getTime() + Long.parseLong(expiration));

          return Jwts.builder()
                       .setIssuer("API da equipe studyjava") 
                       .setSubject(logado.getId().toString()) 
                       .setIssuedAt(hoje)  
                       .setExpiration(dataExpiracao) 
                       .signWith(SignaturedAlgorithm.HS256, secret) 
                       .compact()  
         ;
     }
}

5. Devolvendo o token para o cliente Para retornar o token gerado iremos criar a classe TokenDTO e, através do construtor, atribuiremos a ela o token e seu tipo e a retornaremos como resposta em nosso controller. Quem e como o token será armazenado é de responsabilidade do frontend.

package br.com.studyjava.dto;

public class TokenDTO {
     private String token;
     private String tipo;

     public TokenDTO(String token, String tipo) {
          this.token = token;
          this.tipo = tipo;
     }

     // getters
}
-----------------------------------------------------------------------------------
@RestController
@RequestMapping("/auth")
public class AutenticacaoController {

     @Autowired
     private AuthenticationManager authManager; 

     @Autowired
     private TokenService tokenService; 

     @PostMapping
     public ResponseEntity<?> autenticar(@RequestBody @Valid LoginForm form) {
          UsernamePasswordAuthenticationToken dadosLogin = form.converter();

          try {
              Authentication authentication = authManager.authenticate(dadosLogin);
              String token = tokenService.gerarToken(authentication);
              return ResponseEntity.ok(new TokenDTO(token, "Bearer"));
          } catch(AuthenticationException e) {
              return ResponseEntity.badRequest().build();
         }
     }
}

6. Recebendo o token das requisições do cliente Antes de iniciarmos a leitura do token enviada pelo frontend, vamos repassar como isso tudo ocorre nos bastidores. Podemos deixar o spring security apresentar e controlar a tela de login ou, o que geralmente ocorre, construirmos nossa própria tela e controle. No primeiro caso basta acionar o método formLogin() em nosso contexto de configuração de segurança. Repare que neste caso não haverá um controlador visível. O próprio spring se encarrega de tratar o endpoint / ou /login, realizar a autenticação e responder para o cliente. Mas, como eu disse, geralmente criamos nossa própria tela de login e um controller para mapear a uri de login. Neste artigo usamos /auth. Feito essa introdução podemos apresentar o caminho ou fluxo do token. a) O cliente dispara uma requisição /auth do tipo POST incluindo no body o json com e-mail e senha. b) Nossa aplicação recebe essa requisição através do controller autenticar, valida o login no banco e retorna como resposta o token e o tipo de autenticação. c) O cliente recebe o token e o armazena em cookie, memória, session ou local storage, porque nas próximas requisições ele terá que enviá-lo para nossa API. d) Agora imaginemos que o cliente queira excluir um registro de nossa base. Para isso ele vai precisar nos enviar o token, pois esse tipo de operação é restrita. Se ele tentar enviar essa requisição sem informar o token, receberá um 403 - Forbidden. Então, para ele disparar essa requisição (ex: tipo DELETE na uri /topicos/2) ele tem que adicionar o cabeçalho authorization contendo a palavra Bearer +" " + token. e) Precisamos então receber esse token, revalidá-lo e seguir o fluxo da requisição. Para isso, precisamos interceptar a requisição de delete antes de cair em nosso controller autenticar. Para fazer isso, vamos criar um filtro que chamaremos de AutenticacaoViaTokenFilter e estendê-lo de OncePerRequestFilter, que é um filtro do spring chamado uma única vez a cada requisição e implementar seu método doFilterInternal. f) Neste método, vamos recuperar o token do cabeçalho (recuperarToken), validá-lo (isTokenValido), gravá-lo no contexto de segurança (autenticarToken) e dar seguimento ao fluxo da requisição através do doFilter.

g) Nosso filtro AutenticacaoViaTokenFilter precisa ser registrado para ser usado pelo spring. Faremos isso em nossa classe Security Configuration, no método configure(HttpSecurity http). Usamos para isso o método addFilterBefore, pois o spring precisa saber a ordem de execução dos filtros. Informo para ele que deve executar nosso filtro AutenticacaoViaTokenFilter antes do UsernamePasswordAuthenticationFilter (filter usado internamento pelo spring).

// Crio meu filter.
package br.com.studyjava.config.security;

public class AutenticacaoViaTokenFilter extends OncePerRequestFilter {

     private TokenService tokenService;

     public AutenticacaoViaTokenFilter(TokenService tokenService){
          this.tokenService = tokenService;
     }

     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

            String token = recuparToken(request);
            boolean valido = tokenService.isTokenValido(token);
            if(valido){
                 tokenService.autenticarCliente(token);
            }

            filterChain.doFilter(request, response);
     }
}

private String recuparToken(HttpServletRequest request) {
     String token = request.getHeader("Authorization");
     if(token = null || token.isEmpty() || !token.startsWith("Bearer ")) {
          return null;
     }

     return token.substring(7, token.length());
}
-------------------------------------------------------------------------------------------
// Registro meu filter no spring security.
package br.com.studyjava.config.security;

@EnableWebSecurity
@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter{
   ...
   @Autowired
   private TokenService tokenService; 

   @Override
   @Bean  
   protected AuthenticationManager authenticationManager() throws Exception {
         return supoer.authenticationManager();
   }

   // Configurações de autorização / acesso
   @Override
   public void configure(HttpSecurity http) throws Exception {
       // Consultando todos os tópicos com /topicos
       // Consultando um tópico específico com /topicos/1
       // Qualquer outro request precisa de autenticação do usuário.
       http.authorizeRequest()
              .antMatchers(HttpMethod.GET, "/topicos").permitAll()
              .antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
              .antMatchers(HttpMethod.POST, "/auth").permitAll()
              .anyRequest().authenticated()
              .and().csrf().disable()
              .sessonManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              .and().doFilterBefore(new AutenticacaoViaTokenFilter(tokenService), UsernamePasswordAuthenticationFiler.class)
       ;
   }
------------------------------------------------------------------------------------
// Criando nosso método para validar o token.
@Service
public class TokenService {
     @Value("${studyjava.jwt.expiration}")
     private String expiration;

     @Value("${studyjava.jwt.secrect}")
     private String secret;

     @Autowired
     private UsuarioRepository repoUsuario; 

     public String token gerarToken(Authentication authentication) {
          Usuario logado = (Usuario)authentication.getPrincipal();
          Date hoje = new Date();
          Date dataExpiracao = new Date(hoje.getTime() + Long.parseLong(expiration));

          return Jwts.builder()
                       .setIssuer("API da equipe studyjava") 
                       .setSubject(logado.getId().toString()) 
                       .setIssuedAt(hoje)  
                       .setExpiration(dataExpiracao) 
                       .signWith(SignaturedAlgorithm.HS256, secret) 
                       .compact()  
         ;
     }

     public boolean isTokenValido(String token) {
          try {
              Jwts.parser().setSigningKey(this.secrect).parseClaimsJws(token);
              return true;
          }
              catch(Exception e){
              return false;
          }
     }

     public void autenticarCliente(String token) {
          Long idUsuario = tokenService.getIdUsuario(token);
          Optional<Usuario> usuario = repoUsuario.findById(idUsuario);
          if(usuario.IsPresent()) {
              UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(Usuario, null, usuario.get().getAuthorities());
              SecurityContextHolder.getContext().setAuthentication(authentication);
          }
     }

     public String getCodUsuario(String token) {
        Claims body = Jwts.parser().setSigningKey(this.secret).parseClaimsJws(token).getBody();
        return body.getSubject();
     }
}

7. Logoff Não é função do backend realizar o logoff quando estamos usando token, mas do frontend. Entretanto é possível implementar um blacklist de tokens inválidos. Poderíamos gravar essa lista no banco de dados ou no redis.

8. Consumindo minha API via aplicação javascript No exemplo abaixo os endereços das aplicações frontend que consumiriam meu serviço seriam localhost:3000 e localhost:3000.

@Override
public void addCorsMappings(CorsRegistry registry) {
    //liberando app cliente 1
    registry.addMapping("/**")
         .allowedOrigins("http://localhost:3000")
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT");

    //liberando app cliente 2
    registry.addMapping("/topicos/**")
         .allowedOrigins("http://localhost:4000")
        .allowedMethods("GET", "OPTIONS", "HEAD", "TRACE", "CONNECT");
}

9. Como incluir e recuperar mais informações no token? Vamos incluir os perfis no token para evitar ida ao banco.

dfpinto commented 4 years ago

Mapeando tabela do banco de dados em entidades do spring

Eu só consegui realizar esse mapeamento de forma muito simples pela IDE do netbeans. Seguem os passos:

1. Baixe e instale o netbeans. https://netbeans.org/downloads/old/8.0/

2. Crie um maven project vazio. O netbeans abrirá 3 abas: Projetos, Arquivos e Serviços.

3. Crie uma nova conexão com o bd Clique na aba Serviços. Abra o item Banco de Dados. Abra o item Drivers. 3.1 Se já existir o driver MySql, então clique com botão direito em MySql e depois em Conectar Usando... 3.1.1 Vai abrir um popup geralmente preenchido com as configurações padrão. Se tiver usando o Xampp, então só vai precisar informar o nome do seu database. Exemplo: jdbc:mysql://localhost:3306/meudatabase?zeroDateTimeBehavior=convertToNull. Clique em próximo e depois em finalizar. 3.1.2 O Netbeans vai criar uma string de conexão que aparecerá como um item na aba Serviços. 3.2 Se não existir o driver MySql: 3.2.1 Baixe o drive do mysql em https://dev.mysql.com/downloads/connector/j/. Selecione no combo Operating System a opção Platform Idependent. Formato ZIP arquive. Descompacte-o na pasta desejada. 3.2.2 Clique com o botão direito em cima de Drivers e depois em Novo Driver. Uma tela popup irá abrir. Clique no botão adicionar e selecione o jar descompactado. 3.2.3 Siga os passos 3.1.

4. Mapeando uma tabela Clique com o botão direito no nome do projeto que criou. Clique em Novo e depois em Outros. Escolha a categoria Persistência. Escolha o tipo de arquivo "Classes de Entidade do Banco de Dados". Clique no botão próximo. Escolha a conexão do banco de dados. Quando clicar no combo, vai aparecer a string de conexão criada no item 3.1.2. Selecione-a. Aparecerá todas as tabelas disponíveis para seleção. Selecione uma ou todas as tabelas desejadas. Clique no botão próximo. Você poderá marcar ou desmarcar os checkbox disponíveis. Selecione a opção "lento" para o campo Extrair Associação. Clique no botão finalizar. Uma classe java com mesmo nome da tabela será criada.

PS: Contribuição dada pelo Amauri.

Tem uma outra forma também, porém bem antiga e não recomendo. Segue o link.

Como migrar tabela do banco para o IDE (reengenharia reversa):

http://shengwangi.blogspot.com/2014/12/how-to-create-java-classes-from-tables.html

dfpinto commented 4 years ago

Interceptando os erros do seu projeto Spring

Geralmente usamos bean validation em nossas classes modelo (Entity): @NotEmpty @NotNull etc. Em nossos controladores, anotamos com @Valid e dessa forma, quando a uri é acionada, o Spring acusará um erro, status 400. O problema é que o java vai apresentar esse erro com riqueza de detalhes e não é isso que queremos. O ideal seria mostrar um erro simples, com pouca informação, mas suficiente para o usuário. Para isso, o Spring dispõe da anotação @RestControllerAdvice que vai interceptar todos os erros ocorridos em nossos controladores. Vamos ao passo a passo:

  1. Crie uma classe e anote com @RestControllerAdvice.
@RestControllerAdvice
public class ErroDeValidacaoHandler {
}
  1. Crie um método que irá tratar a exceção e anote com @ExceptionHandler. 2.1. Passe como parâmetro da anotação as exceções oriundas da validação que deseja interceptar. 2.2. Para que o fluxo do erro permaneça com status 400 é necessário anotar o método com @ResponseStatus. Se não o fizer, o Spring retornará o status 200. 2.3. O método precisa retornar a lista de erros.
@RestControllerAdvice
public class ErroDeValidacaoHandler {

    @ResponseStatus(code = HttpStatus._BAD_REQUEST_)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public List<ErroDeFormularioDTO> handle(MethodArgumentNotValidException exception) {
    }
}
  1. Crie uma classe para representar os erros do formulário. 3.1. Sugiro criar um classe Entity ou DTO para que representará os erros do formulário. Chamaremos essa classe de ErroDeFormularioDTO.

    
    public class ErroDeFormularioDTO {
    private String campo;
    private String erro;
    
    public ErroDeFormularioDTO(String campo, String erro) {
        this.campo = campo;
        this.erro = erro;
    }
    
    public String getCampo() {
        return this.campo;
    }
    
    public String getErro() {
        return this.erro;
    }

}

@RestControllerAdvice public class ErroDeValidacaoHandler {

@ResponseStatus(code = HttpStatus._BAD_REQUEST_)
@ExceptionHandler(MethodArgumentNotValidException.class)
public List<ErroDeFormularioDTO> handle(MethodArgumentNotValidException exception) {
}

}

4. **Percorra a lista de erros interceptada pelo método handler e atribua a nossa lista ErroDeFormularioDTO;**
O parâmetro exception conterá a lista de erros interceptada.
Para recuperar a mensagem de erro iremos injetar a classe MessageSource do Sprint que tratar internacionalização.

@RestControllerAdvice public class ErroDeValidacaoHandler {

@Autowired
private MessageSource messageSource;

@ResponseStatus(code = HttpStatus._BAD_REQUEST_)
@ExceptionHandler(MethodArgumentNotValidException.class)
public List<ErroDeFormularioDTO> handle(MethodArgumentNotValidException exception) {
    List<ErroDeFormularioDTO> dto = new ArrayList<>();
    List<FieldError> fieldErrors = exception.getBindingResult().getFieldErros();
    fieldErros.forEach(e -> {
        String mensagem = messageSource.getMessage(e, LocaleContextHolder.getLocale());
        ErroDeFormularioDTO erro = new ErroDeFormularioDTO(e.getField(), mensagem);
        dtp.add(erro);
    }

    return dto;
}

}


5. **Testando internacionalização.**
Se desejar testar sua aplicação usando um outro idioma, basta incluir o atributo _Accept-Language_ no _Header_ do request para _en-US_, por exemplo.
![Mensagem em portugues](https://user-images.githubusercontent.com/37200499/93718023-c556a700-fb4f-11ea-9993-9dc3f949b970.png)
![Mensagem em ingles 1](https://user-images.githubusercontent.com/37200499/93718038-d7d0e080-fb4f-11ea-964e-b4ff65f394f7.png)
![Mensagem em ingles 2](https://user-images.githubusercontent.com/37200499/93718042-db646780-fb4f-11ea-8a2b-29274ebc45e7.png)
robsoncalixto commented 3 years ago

Construindo um Client para consumir WS Soap

  1. Ferramentas e versões

    • Spring Tool Suite (4.5.RELEASE)
    • Java (version: 8)
    • Spring boot (2.3.4.RELEASE)
  2. Verificando a wsdl

image

  1. Incluindo dependências e configuração no pom.xml -spring-ws-core -maven-jaxb2-plugin
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>0.13.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <schemaLanguage>WSDL</schemaLanguage>
                    <generatePackage>deputados_ws</generatePackage>
                    <schemas>
                        <schema>
                            <url>https://www.camara.leg.br/SitCamaraWS/Deputados.asmx?wsdl</url>
                        </schema>
                    </schemas>
                </configuration>
            </plugin>           
        </plugins>
    </build>

Depois de finalizado a configuração é preciso atualizar o projeto. image

Quando atualizado será criada uma package dentro do projeto, o nome será o informado dentro do pom, na tag generatePackage e a estrutura final do projeto deve ficar como a imagem. image

  1. Criando uma classe para extender WebServiceGatewaySupport para nos permitir fazer o request via WebserviceTemplate

ClienteDeputadosWS .java

import java.net.URISyntaxException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.SoapMessage;

import deputados_ws.ObterDeputados;
import deputados_ws.ObterDeputadosResponse;

public class ClienteDeputadosWS  extends WebServiceGatewaySupport{

    private static final Logger log = LoggerFactory.getLogger(ClienteDeputadosWS.class);

    public ObterDeputadosResponse getDeputados() throws URISyntaxException {
        ObterDeputados request = new ObterDeputados();
        log.info("Request Deputados");      
        return (ObterDeputadosResponse) getWebServiceTemplate().marshalSendAndReceive(request, new WebServiceMessageCallback() {
            public void doWithMessage(WebServiceMessage message) {
                ((SoapMessage)message).setSoapAction("https://www.camara.gov.br/SitCamaraWS/Deputados/ObterDeputados");
            }
        });
    }

}
  1. Configurando o componente web service Spring WS usa Spring Framework’s OXM modulo que utiliza Jaxb2Marshaller para serializar e deserializar XML.

ClienteConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Configuration
public class ClienteConfig {

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("deputados_ws");
        return marshaller;
    }

    @Bean
    public ClienteDeputadosWS deputadosCliente(Jaxb2Marshaller marshaller) {
        ClienteDeputadosWS cliente = new ClienteDeputadosWS();
        cliente.setDefaultUri("https://www.camara.leg.br/SitCamaraWS/Deputados.asmx");      
        cliente.setMarshaller(marshaller);
        cliente.setUnmarshaller(marshaller);
        return cliente;
    }

}
  1. Criando uma classe para execução do cliente

executeCliente.java


import java.net.URISyntaxException;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import deputados_ws.ObterDeputadosResponse.ObterDeputadosResult;

public class executeCliente {

    public static void main(String[] args) throws URISyntaxException {
         try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ClienteConfig.class)) {
            ClienteDeputadosWS cliente = context.getBean(ClienteDeputadosWS.class);

             ObterDeputadosResult response = cliente.getDeputados(); 
        }

    }
}

https://docs.spring.io/spring-ws/docs/2.2.x/reference/html/client.html