Capítulo 8

Apêndice: Criando uma nova Conta

Apêndice não faz parte do curso. Os apêndices são conteúdos adicionais que não fazem parte da carga horária regular do curso. São conteúdos extras para direcionar seus estudos após o curso.

8.1 Implementando Segurança em nosso Sistema

Quando queremos criar uma nova conta em alguma aplicação, geralmente existe um formulário onde preenchemos nossa conta de e-mail.

E ao submetermos esse formulário é enviado um e-mail para a conta informada com um link para criação da nossa conta. Esse link nos leva a outro formulário onde precisamos preencher a senha e a confirmação da senha e as vezes um login.

Vamos implementar essa funcionalidade em nossa aplicação. Para isso precisamos ter um link na tela de login que irá levar um visitante para o formulário de solicitação de acesso.

Vamos adicionar na página de login um link para que os visitantes possam efetuar o cadastro.

    ...
    <button class="btn btn-primary" type="submit">Entrar</button>

    <a href="/usuario/request">ou cadastrar-se</a>
    ...

Precisamos agora implementar um Controller que ficará responsável por lidar com as requests_para usuário. Vamos criar a classe UsuarioController e implementar um método com um mapeamento _GET para a URI /usuario/request

    @Controller
    public class UsuarioController {
        @GetMapping("/usuario/request")
        public ModelAndView formSolicitacaoDeAcesso(){
            return new ModelAndView("usuario/form-email");
        }
    }

Agora precisamo criar o formulário para o usuário informar o e-mail ao qual ele quer solicitar acesso. Vamos criar o arquivo usuario/form-email.jsp:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8" %>
    <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
    <%@ taglib tagdir="/WEB-INF/tags/" prefix="ingresso" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

    <ingresso:template>
        <jsp:body>
            <div class=" col-md-6 col-md-offset-3">
                <form action="/usuario/request" method="post">
                    <span class="text-danger">${param.error}</span>

                    <div class="form-group">
                        <label for="login">E-mail:</label>
                        <input id="login" type="text" name="email" class="form-control">
                    </div>

                    <button class="btn btn-primary" type="submit">Solicitar Acesso</button>
                </form>
            </div>
        </jsp:body>
    </ingresso:template>

Ao submeter esse formulário vamos levar o usuário para uma página que informe a ele que, enviamos um e-mail para a criação do acesso. Para isso vamos criar o arquivo usuario/adicionado.jsp:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
            pageEncoding="UTF-8" %>
    <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
    <%@ taglib tagdir="/WEB-INF/tags/" prefix="ingresso" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

    <ingresso:template>
        <jsp:body>
            <h3>Usuário criado com sucesso!</h3>

            <p>
                Enviamos um e-mail com um link de confirmação para <strong>${usuario.email}</strong>.<br/>

                Por favor confirme a criação do seu usuário, para liberar seu acesso.
            </p>

        </jsp:body>
    </ingresso:template>

Como implementamos segurança em nossa aplicação precisamos liberar o acesso para URIs em /usuario.

    @EnableWebSecurity
    @ImportResource("/WEB-INF/spring-context.xml")
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Autowired
        private UserDetailsService userDetailsService;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .csrf().disable().authorizeRequests()                    
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .antMatchers("/compra/**").hasRole("COMPRADOR")
                    .antMatchers("/usuario/**").permitAll() // <== nova linha
                    .antMatchers("/filme/**").permitAll()
                    .antMatchers("/sessao/**/lugares").permitAll()
                    .antMatchers("/magic/**").permitAll()
                    .antMatchers("/").permitAll()
                .anyRequest()
                    .authenticated()
                .and()
                    .formLogin()
                        .usernameParameter("email")
                        .loginPage("/login")
                        .permitAll()
                .and()
                    .logout()
                        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                        .permitAll();

        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {

            auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
        }

        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/assets/**");
        }

    }

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos online que lançamos na plataforma Alura. Você estuda a qualquer momento com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

8.2 Exercício - Criando formulário de solicitação de acesso

  1. Na página login.jsp, adicione o link para cadastrar um novo usuário após o botão de Entrar:

     ...
     <button class="btn btn-primary" type="submit">Entrar</button>
    
     <a href="/usuario/request">ou cadastrar-se</a>
     ...
    
  2. Crie a classe UsuarioController com um mapeamento GET para /usuario/request/ e que retorne para página usuario/form-email:

     @Controller
     public class UsuarioController {
         @GetMapping("/usuario/request")
         public ModelAndView formSolicitacaoDeAcesso(){
             return new ModelAndView("usuario/form-email");
         }
     }
    
  3. Crie a página usuario/form-email.jsp:

     <%@ page language="java" contentType="text/html; charset=UTF-8"
             pageEncoding="UTF-8" %>
     <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
     <%@ taglib tagdir="/WEB-INF/tags/" prefix="ingresso" %>
     <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
     <ingresso:template>
         <jsp:body>
             <div class=" col-md-6 col-md-offset-3">
                 <form action="/usuario/request" method="post">
                     <span class="text-danger">${param.error}</span>
    
                     <div class="form-group">
                         <label for="login">E-mail:</label>
                         <input id="login" type="text" name="email" class="form-control">
                     </div>
    
                     <button class="btn btn-primary" type="submit">Solicitar Acesso</button>
                 </form>
             </div>
         </jsp:body>
     </ingresso:template>
    
  4. Crie um mapeamento POST para /usuario/request que retorne para página usuario/adicionado.jsp:

     @PostMapping("/usuario/request")
     public ModelAndView solicitacaoDeAcesso(String email){
    
         ModelAndView view = new ModelAndView("usuario/adicionado");
    
         return view;
     }
    
  5. Crie a página usuario/adicionado.jsp:

     <%@ page language="java" contentType="text/html; charset=UTF-8"
             pageEncoding="UTF-8" %>
     <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
     <%@ taglib tagdir="/WEB-INF/tags/" prefix="ingresso" %>
     <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
     <ingresso:template>
         <jsp:body>
             <h3>Usuário criado com sucesso!</h3>
    
             <p>
                 Enviamos um e-mail com um link de confirmação para <strong>${usuario.email}</strong>.<br/>
    
                 Por favor confirme a criação do seu usuário, para liberar seu acesso.
             </p>
    
         </jsp:body>
     </ingresso:template>
    
  6. Libere o acesso para URI /usuario na classe SecurityConfiguration:

     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http
             .csrf().disable().authorizeRequests()                
                 .antMatchers("/admin/**").hasRole("ADMIN")
                 .antMatchers("/compra/**").hasRole("COMPRADOR")
                 .antMatchers("/usuario/**").permitAll() // <== nova linha
                 .antMatchers("/filme/**").permitAll()
                 .antMatchers("/sessao/**/lugares").permitAll()
                 .antMatchers("/magic/**").permitAll()
                 .antMatchers("/").permitAll()
             .anyRequest()
                 .authenticated()
             .and()
                 .formLogin()
                     .usernameParameter("email")
                     .loginPage("/login")
                     .permitAll()
             .and()
                 .logout()
                     .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                     .permitAll();
    
     }
    

8.3 Configurações para envio de e-mails

Agora que temos a primeira parte do fluxo pronta, vamos implementar o envio do e-mail. O e-mail que queremos enviar deve ter um identificador único vinculado a conta que foi informada no formulário de solicitação de acesso.

Dessa forma não precisamos que o usuário preencha novamente a sua conta de e-mail. Esse identificador único pode ser qualquer coisa, um número, uma combinação de letras. No nosso caso iremos utilizar a classe UUID (Unique identifier).

Para dar mais semântica ao nosso código vamos modelar algo que represente, o vínculo entre o identificador único e a conta de e-mail informada. Para isso vamos criar a classe Token

    @Entity
    public class Token {

        @Id
        private String uuid;

        @Email
        private String email;

        public Token(){}

        public Token(String email) {
            this.email = email;
        }

        //getters e setters
    }

Para não nos preocuparmos com a geração do UUID vamos adicionar um Callback da JPA na nossa entidade Token. Esses callbacks podem ser comparados com as Triggers em um banco dados. Ou seja antes ou depois de determinados eventos (Insert, Update, Delete) podemos executar algum código.

No nosso caso queremos que antes de salvar um Token no banco de dados, ele gere o identificador UUID. para isso vamos criar um método e anotá-lo com @PrePersist, e nele preencher o atributo uuid:

    @Entity
    public class Token {
        //... restante da implementação

        @PrePersist
        public void prePersist(){
            uuid = UUID.randomUUID().toString();
        }
    }

Os e-mails enviado a partir de uma aplicação geralmente seguem um template, e podemos ter mais de um tipo de e-mails em nossa aplicação. Mais uma vez podemos nos aproveitar do Design Pattern Strategy para criar tipos diferentes de templates de e-mails.

Para isso vamos criar uma interface Email:

    public interface Email {
        String getTo();
        String getBody();
        String getSubject();
    }

Vamos criar também um template de e-mail para solicitação de acesso.

    public class EmailNovoUsuario implements Email {
        private final Token token;

        public EmailNovoUsuario(Token token) {
            this.token = token;
        }

        @Override
        public String getTo() {
            return token.getEmail();
        }

        @Override
        public String getBody() {
            StringBuilder body = new StringBuilder("<html>");
            body.append("<body>");
                body.append("<h2> Bem Vindo </h2>");
                body.append(String.format("Acesso o <a href=%s>link</a> para para criar seu login no sistema de ingressos.", makeURL() ));
            body.append("</body>");
            body.append("</html>");

            return body.toString();
        }

        @Override
        public String getSubject() {
            return "Cadastro Sistema de Ingressos";
        }

        private String makeURL() {
            return String.format("http://localhost:8080/usuario/validate?uuid=%s", token.getUuid());
        }
    }

Agora precisamos criar uma classe que será responsável por enviar e-mails a partir da nossa aplicação. Essa classe deve receber um objeto Email e disparar o e-mail.

    @Component
    public class Mailer {

        @Autowired
        private JavaMailSender sender;

        private final String from = "Ingresso<cursofj22@gmail.com>";


        public void send(Email email) {
            MimeMessage message = sender.createMimeMessage();

            MimeMessageHelper messageHelper = new MimeMessageHelper(message);


            try {

                messageHelper.setFrom(from);
                messageHelper.setTo(email.getTo());
                messageHelper.setSubject(email.getSubject());
                messageHelper.setText(email.getBody(), true);

                sender.send(message);

            } catch (MessagingException e) {
                throw new IllegalArgumentException(e);
            }
        }
    }

Para que o Spring consiga enviar e-mails, precisamos configurar o endereço do servidor de e-mail, porta, usuário, senha entre outras configurações.

Podemos observar essas configurações no arquivo spring-context.xml:

    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="smtp.gmail.com"/>
        <property name="port" value="587"/>
        <property name="username" value="algum@gmail.com"/>
        <property name="password" value="UmaSenhaSuperSegura"/>

        <property name="javaMailProperties">
            <props>
                <prop key="mail.smtp.auth">true</prop>
                <prop key="mail.smtp.starttls.enable">true</prop>
                <prop key="mail.debug">true</prop>
            </props>
        </property>
    </bean>

8.4 Exercício - Configurando o envio de e-mails

  1. Vamos aplicar mais uma vez o Strategy para que possamos ter vários tipos de e-mails na nossa aplicação. Para isso crie a interface Email no pacote br.com.caelum.ingresso.mail:

     public interface Email {
         String getTo();
         String getBody();
         String getSubject();
     }
    
  2. Vamos criar o Token que será enviado no nosso e-mail de novo usuário:

     @Entity
     public class Token {
    
         @Id
         private String uuid;
    
         @Email
         private String email;
    
         public Token(){}
    
         public Token(String email) {
             this.email = email;
         }
    
         //getters e setters
    
         @PrePersist
         public void prePersist(){
             uuid = UUID.randomUUID().toString();
         }
     }
    
  3. Vamos criar uma implementação da interface Email que represente o e-mail de novo usuário. Para isso crie a classe EmailNovoUsuario no pacote br.com.caelum.ingresso.mail e faça com que ela implemente a interface Email:

     public class EmailNovoUsuario implements Email {
         private final Token token;
    
         public EmailNovoUsuario(Token token) {
             this.token = token;
         }
    
         @Override
         public String getTo() {
             return token.getEmail();
         }
    
         @Override
         public String getBody() {
             StringBuilder body = new StringBuilder("<html>");
             body.append("<body>");
             body.append("<h2> Bem Vindo </h2>");
             body.append(String.format("Acesso o <a href=%s>link</a> para para criar seu login no sistema de ingressos.", makeURL() ));
             body.append("</body>");
             body.append("</html>");
    
             return body.toString();
         }
    
         @Override
         public String getSubject() {
             return "Cadastro Sistema de Ingressos";
         }
    
         private String makeURL() {
             return String.format("http://localhost:8080/usuario/validate?uuid=%s", token.getUuid());
         }
     }
    
  4. Crie Mailer que será responsável por enviar e-mails a partir da nossa aplicação:

     @Component
     public class Mailer {
    
         @Autowired
         private JavaMailSender sender;
    
         private final String from = "Ingresso<cursofj22@gmail.com>";
    
         public void send(Email email) {
             MimeMessage message = sender.createMimeMessage();
    
             MimeMessageHelper messageHelper = new MimeMessageHelper(message);
    
             try {
    
                 messageHelper.setFrom(from);
                 messageHelper.setTo(email.getTo());
                 messageHelper.setSubject(email.getSubject());
                 messageHelper.setText(email.getBody(), true);
    
                 sender.send(message);
    
             } catch (MessagingException e) {
                 throw new IllegalArgumentException(e);
             }
         }
     }
    

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam tecnicamente o assunto para revisar os livros a fundo. Não têm anos de experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

8.5 Salvando token e enviando e-mail

Agora que já temos a configuração para envio de e-mails, temos que persistir o Token no banco e disparar um e-mail para a conta informada no formulário.

Para isso vamos criar a classe TokenDao que irá persistir o Token no banco de dados:

    @Repository
    public class TokenDao {
        @PersistenceContext
        private EntityManager manager;

        public void save(Token token) {
            manager.persist(token);
        }        
    }

Além disso precisamos de alguém que use a classe TokenDao para persistir e ainda assim devolva o Token para enviarmos o e-mail. Vamos criar a classe que irá nos ajudar com a manipulação de Tokens.

    @Component
    public class TokenHelper {
        @Autowired
        private TokenDao dao;

        public Token generateFrom(String email) {

            Token token = new Token(email);

            dao.save(token);

            return token;
        }
    }

Agora só precisamos alterar o método solicitacaoDeAcesso na classe UsuarioController para que ele use o TokenHelper e Mailer para salvar o Token e enviar o e-mail. Para isso vamos injetar TokenHelper e Mailer na classe UsuarioController e alterar o método solicitacaoDeAcesso

    @Controller
    public class UsuarioController {

        @Autowired
        private Mailer mailer;

        @Autowired
        private TokenHelper tokenHelper;

        @PostMapping("/usuario/request")
        @Transactional
        public ModelAndView solicitacaoDeAcesso(String email){

            ModelAndView view = new ModelAndView("usuario/adicionado")

            Token token = tokenHelper.generateFrom(email);

            mailer.send( new EmailNovoUsuario(token) );

            return view;
        }
    }

8.6 Exercício - Enviando e-mail

  1. Antes de enviar o e-mail precisamos gerar um token para o novo usuário. Para isso vamos criar a classe TokenDao:

     @Repository
     public class TokenDao {
         @PersistenceContext
         private EntityManager manager;
    
         public void save(Token token) {
             manager.persist(token);
         }        
     }
    
  2. Para criar um novo e-mail de cadastro precisamos do Token salvo no banco. Para isso vamos criar a classe TokenHelper no pacote br.com.caelum.ingresso.helper:

     @Component
     public class TokenHelper {
         @Autowired
         private TokenDao dao;
    
         public Token generateFrom(String email) {
    
             Token token = new Token(email);
    
             dao.save(token);
    
             return token;
         }
     }
    
  3. Injete a classe Mailer e TokenHelper em UsuarioController:

     @Controller
     public class UsuarioController {
    
         @Autowired
         private Mailer mailer;
    
         @Autowired
         private TokenHelper tokenHelper;
    
         // ... demais implementações
    
     }
    
  4. Altere o método solicitacaoDeAcesso na classe UsuarioController pare que ele envie o e-mail com um token para o usuário:

     @PostMapping("/usuario/request")
     @Transactional
     public ModelAndView solicitacaoDeAcesso(String email){
    
         ModelAndView view = new ModelAndView("usuario/adicionado")
    
         Token token = tokenHelper.generateFrom(email);
    
         mailer.send( new EmailNovoUsuario(token) );
    
         return view;
     }
    

8.7 Implementando validação

Agora precisamos nos preocupar com a segunda parte da liberação de acesso.

Quando o usuário clicar no link do e-mail ele deve ser redirecionado para um formulário solicitando a senha e a confirmação de senha mas somente se o Token que estiver no link for válido. Ou seja se o Token existir no banco de dados.

Vamos criar um método na classe UsuarioController chamado validaLink e este por sua vez deve pegar o parâmetro uuid que adicionamos no link.


    @GetMapping("/usuario/validate")
    public ModelAndView validaLink(@RequestParam("uuid") String uuid){

    }

Vamos implementar um método na classe TokenDao que deve retornar um Token a partir de um uuid caso ele exista.

    public Optional<Token> findByUuid(String uuid) {
        return manager.createQuery("select t from Token t where t.uuid = :uuid", Token.class)
                .setParameter("uuid", uuid)
                    .getResultList()
                        .stream()
                            .findFirst();
    }

Como não acessamos TokenDao diretamente no nosso controller vamos criar um método chamado getTokenFrom na classe TokenHelper.

    public Optional<Token> getTokenFrom(String uuid) {
        return dao.findByUuid(uuid);
    }

Agora vamos alterar o método validaLink para que ele pegue o Token a partir do uuid. Caso o Token não exista, vamos redirecionar o usuário para a tela de Login com uma mensagem.

    @GetMapping("/usuario/validate")
    public ModelAndView validaLink(@RequestParam("uuid") String uuid){

        Optional<Token> optionalToken = tokenHelper.getTokenFrom(uuid);

        if (!optionalToken.isPresent()){

            ModelAndView view = new ModelAndView("redirect:/login");

            view.addObject("msg", "O token do link utilizado não foi encontrado!");

            return view;

        }
    }

Caso o Token exista precisamos redirecionar o usuário para o formulário que irá solicitar a senha e a confirmação de senha.

Como não temos um modelo que represente Token, senha e confirmação de senha. Vamos começar criando um DTO chamado ConfirmacaoLoginForm e vamos aproveitar e já implementar um método que valida se a senha e a confirmação de senha são iguais.

    public class ConfirmacaoLoginForm {
        private Token token;
        private String password;
        private String confirmPassword;

        public ConfirmacaoLoginForm(){}

        public ConfirmacaoLoginForm(Token token) {
            this.token = token;
        }

        //getters e setters

        public boolean isValid(){
            return password.equals(confirmPassword);
        }
    }

Agora vamos redirecionar para o formulário que deve usar o DTO para receber os dados do formulário:


    @GetMapping("/usuario/validate")
    public ModelAndView validaLink(@RequestParam("uuid") String uuid){

        Optional<Token> optionalToken = tokenHelper.getTokenFrom(uuid);

        if (!optionalToken.isPresent()){

            ModelAndView view = new ModelAndView("redirect:/login");

            view.addObject("msg", "O token do link utilizado não foi encontrado!");

            return view;
        }

        Token token = optionalToken.get();
        ConfirmacaoLoginForm confirmacaoLoginForm = new ConfirmacaoLoginForm(token);

        ModelAndView view = new ModelAndView("usuario/confirmacao");

        view.addObject("confirmacaoLoginForm", confirmacaoLoginForm);

        return view;
    }

Agora precisamos criar a página do formulário de senha e confirmação de senha:

    <ingresso:template>
        <jsp:body>
            <form action="/usuario/cadastrar" method="post">
                <span class="text-danger">${param.error}</span>


                <input type="hidden" name="token.uuid" value="${confirmacaoLoginForm.token.uuid}">
                <input type="hidden" name="token.email" value="${confirmacaoLoginForm.token.email}">

                <div class="form-group">
                    <label for="password">Senha:</label>
                    <input id="password" type="password" name="password" class="form-control">
                </div>

                <div class="form-group">
                    <label for="confirmPassword">Senha:</label>
                    <input id="confirmPassword" type="password" name="confirmPassword" class="form-control">
                </div>

                <button class="btn btn-primary" type="submit">Cadastrar</button>

            </form>
        </jsp:body>
    </ingresso:template>

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de ensino que favorece o aprendizado com a qualidade reconhecida da Caelum. Você pode escolher um curso nas áreas de Programação, Front-end, Mobile, Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

8.8 Exercício - Validando token

  1. Crie um método validaLink na classe UsuarioController com mapeamento GET para /usuario/validate. E faça com que ele receba o parâmetro uuid a partir da request:

     @GetMapping("/usuario/validate")
     public ModelAndView validaLink(@RequestParam("uuid") String uuid){
    
     }
    
  2. Adicione o método findByUuid na classe TokenDao

     public Optional<Token> findByUuid(String uuid) {
         return manager.createQuery("select t from Token t where t.uuid = :uuid", Token.class)
                 .setParameter("uuid", uuid)
                     .getResultList()
                         .stream()
                             .findFirst();
     }
    
  3. Crie o método getTokenFrom na classe TokenHelper que recebrá uma String com o UUID. Esse método deve buscar o token no banco de dados:

     public Optional<Token> getTokenFrom(String uuid) {
         return dao.findByUuid(uuid);
     }
    
  4. Altere o método validaLink para que ele use o método getTokenFrom da classe TokenHelper. Caso o token não esteja presente, deve retornar para página de login:

     @GetMapping("/usuario/validate")
     public ModelAndView validaLink(@RequestParam("uuid") String uuid){
    
         Optional<Token> optionalToken = tokenHelper.getTokenFrom(uuid);
    
         if (!optionalToken.isPresent()){
    
             ModelAndView view = new ModelAndView("redirect:/login");
    
             view.addObject("msg", "O token do link utilizado não foi encontrado!");
    
             return view;
    
         }
     }
    
  5. Caso o Token esteja presente devemos redirecionar o usuário para uma página, onde ele irá digitar a senha e confirmar a senha. Vamos criar um DTO para esse formulário. Crie a classe ConfirmacaoLoginForm:

     public class ConfirmacaoLoginForm {
         private Token token;
         private String password;
         private String confirmPassword;
    
         public ConfirmacaoLoginForm(){}
    
         public ConfirmacaoLoginForm(Token token) {
             this.token = token;
         }
    
         //getters e setters
    
         public boolean isValid(){
             return password.equals(confirmPassword);
         }
     }
    
  6. Altere o método validaLink para que seja redirecionado para página usuario/confirmacao caso o Token esteja presente:

     @GetMapping("/usuario/validate")
     public ModelAndView validaLink(@RequestParam("uuid") String uuid){
    
         Optional<Token> optionalToken = tokenHelper.getTokenFrom(uuid);
    
         if (!optionalToken.isPresent()){
    
             ModelAndView view = new ModelAndView("redirect:/login");
    
             view.addObject("msg", "O token do link utilizado não foi encontrado!");
    
             return view;
         }
    
         Token token = optionalToken.get();
         ConfirmacaoLoginForm confirmacaoLoginForm = new ConfirmacaoLoginForm(token);
    
         ModelAndView view = new ModelAndView("usuario/confirmacao");
    
         view.addObject("confirmacaoLoginForm", confirmacaoLoginForm);
    
         return view;
     }
    
  7. Crie a página usuario/confirmacao.jsp:

     <ingresso:template>
         <jsp:body>
             <form action="/usuario/cadastrar" method="post">
                 <span class="text-danger">${param.error}</span>
    
                 <input type="hidden" name="token.uuid" value="${confirmacaoLoginForm.token.uuid}">
                 <input type="hidden" name="token.email" value="${confirmacaoLoginForm.token.email}">
    
                 <div class="form-group">
                     <label for="password">Senha:</label>
                     <input id="password" type="password" name="password" class="form-control">
                 </div>
    
                 <div class="form-group">
                     <label for="confirmPassword">Senha:</label>
                     <input id="confirmPassword" type="password" name="confirmPassword" class="form-control">
                 </div>
    
                 <button class="btn btn-primary" type="submit">Cadastrar</button>
    
             </form>
         </jsp:body>
     </ingresso:template>
    

8.9 Persistindo o usuário

Quando o usuário submeter o formulário com a senha e confirmação de senha, precisamos verificar se o formulário está valido. E em caso positivo devemos salvar o usuário com a senha criptografada.

Vamos começar implementando o método que irá receber os dados do formulário de confirmação e fazer a validação do formulário.

    @PostMapping("/usuario/cadastrar")
    public ModelAndView cadastrar(ConfirmacaoLoginForm form){
        ModelAndView view = new ModelAndView("redirect:/login");

        if ( form.isValid() ) {
            // Salvar usuário no banco
        }

        view.addObject("msg", "O token do link utilizado não foi encontrado!");

        return view;
    }

Precisamos pegar um usuário a partir do formulário. Para isso vamos criar o método toUsuario na classe ConfirmacaoLoginForm, esse método deve pegar um usuário no banco de dados caso exista ou criar um novo usuário.

Além disso ele deve criptografar a senha usando BCrypt.

    public class ConfirmacaoLoginForm {

        //... restante da implementação

        public Usuario toUsuario(UsuarioDao dao, PasswordEncoder encoder) {

            String encriptedPassword = encoder.encode(this.password);

            String email = token.getEmail();

            Usuario usuario= dao.findByEmail(email).orElse(novoUsuario(email, encriptedPassword));

            usuario.setPassword(encriptedPassword);

            return usuario;
        }

        private Usuario novoUsuario(String email, String password){
            Set<Permissao> permissoes = new HashSet<>();
            permissoes.add(Permissao.COMPRADOR);

            return new Usuario(email, password, permissoes);
        }
    }

Para que o método toUsuario funcione vamos implementar o método findByEmail na classe UsuarioDao

    @Repository
    public class UsuarioDao {

        @PersistenceContext
        private EntityManager manager;

        public Optional<Usuario> findByEmail(String email) {

            return manager
                    .createQuery("select u from Usuario  u where u.email = :email", Usuario.class)
                        .setParameter("email", email)
                            .getResultList()
                                .stream()
                                    .findFirst();
        }

    }

Vamos alterar o método cadastrar na classe UsuarioController para que ele pegue o usuário do formulário e posteriormente salve o mesmo.

    @PostMapping("/usuario/cadastrar")
    @Transactional
    public ModelAndView cadastrar(ConfirmacaoLoginForm form){
        ModelAndView view = new ModelAndView("redirect:/login");

        if ( form.isValid() ) {
            Usuario usuario = form.toUsuario(usuarioDao, passwordEncoder);

            usuarioDao.save(usuario);

            view.addObject("msg", "Usuario cadastrado com sucesso!");

            return view;
        }

        view.addObject("msg", "O token do link utilizado não foi encontrado!");

        return view;
    }

Por fim vamos implementar o método save na classe UsuarioDao:

    public void save(Usuario usuario) {

        if (usuario.getId() == null)
            manager.persist(usuario);
        else
            manager.merge(usuario);
    }

8.10 Exercício - Cadastrando usuário

  1. Crie o método cadastrar na classe UsuarioController com mapeamento POST para /usuario/cadastrar e verifique se o formulário é válido:

     @PostMapping("/usuario/cadastrar")
     public ModelAndView cadastrar(ConfirmacaoLoginForm form){
         ModelAndView view = new ModelAndView("redirect:/login");
    
         if ( form.isValid() ) {
             // Salvar usuário no banco
         }
    
         view.addObject("msg", "O token do link utilizado não foi encontrado!");
    
         return view;
     }
    
  2. Adicione o método toUsuario na classe ConfirmacaoLoginForm:

     public class ConfirmacaoLoginForm {
    
         //... restante da implementação
    
         public Usuario toUsuario(UsuarioDao dao, PasswordEncoder encoder) {
    
             String encriptedPassword = encoder.encode(this.password);
    
             String email = token.getEmail();
    
             Usuario usuario= dao.findByEmail(email).orElse(novoUsuario(email, encriptedPassword));
    
             usuario.setPassword(encriptedPassword);
    
             return usuario;
         }
    
         private Usuario novoUsuario(String email, String password){
             Set<Permissao> permissoes = new HashSet<>();
             permissoes.add(Permissao.COMPRADOR);
    
             return new Usuario(email, password, permissoes);
         }
     }
    
  3. Crie a classe UsuarioDao e implemente o método findByEmail, que deve retornar um usuário a partir do e-mail se existir:

     @Repository
     public class UsuarioDao {
    
         @PersistenceContext
         private EntityManager manager;
    
         public Optional<Usuario> findByEmail(String email) {
    
             return manager
                     .createQuery("select u from Usuario  u where u.email = :email", Usuario.class)
                         .setParameter("email", email)
                             .getResultList()
                                 .stream()
                                     .findFirst();
         }
    
     }
    
  4. Caso o formulário seja válido devemos persistir o usuário no banco de dados. Para isso altere o método cadastrar na classe UsuarioController:

     @PostMapping("/usuario/cadastrar")
     @Transactional
     public ModelAndView cadastrar(ConfirmacaoLoginForm form){
         ModelAndView view = new ModelAndView("redirect:/login");
    
         if ( form.isValid() ) {
             Usuario usuario = form.toUsuario(usuarioDao, passwordEncoder);
    
             usuarioDao.save(usuario);
    
             view.addObject("msg", "Usuario cadastrado com sucesso!");
    
             return view;
         }
    
         view.addObject("msg", "O token do link utilizado não foi encontrado!");
    
         return view;
     }
    
  5. Adicione o método save na classe UsuarioDao:

     public void save(Usuario usuario) {
    
         if (usuario.getId() == null)
             manager.persist(usuario);
         else
             manager.merge(usuario);
     }
    

Saber inglês é muito importante em TI

Na Alura Língua você reforça e aprimora seu inglês! Com Spaced Repetitions a metodologia se adapta ao seu conhecimento. Com FlashCards, os exercícios são objetivos e focados em uma habilidade específica, para praticar tudo que aprendeu em cada lição. Diversas estatísticas indicarão como prosseguir, analizando quais são seus pontos fortes e fracos, frases e estruturas já conhecidas.

Pratique seu inglês na Alura Língua.