Solucionado (ver solução)
Solucionado
(ver solução)
18
respostas

Falha na autenticação

Boa tarde,

Estou tendo problemas após a validação por email já que estou redirecionando para a view de login e nela estou recebendo duas mensagens, "Usuário não autorizado" e "Usuário cadastrado com sucesso!". Minha classe UsuarioAutenticado:

@Inject
    private UsuarioDAO usuarioDAO;

    public String getUserName(Context context){
         String email = context.session().get(AUTH);
         Optional<Usuario> possivelUsuario = usuarioDAO.comEmail(email);
         if(possivelUsuario.isPresent()){
             return possivelUsuario.get().getNome();
         }
         return null;
    }

    public Result onUnauthorized(Context context) {
        context.flash().put("danger", "Não autorizado!");
        return redirect(routes.UsuarioController.formularioDeLogin());
    }

E meu método confirmaCadastro:

public Result confirmaCadastro(String email, String codigo) {
        Optional<TokenDeCadastro> possivelToken = tokenDeCadastroDAO.comCodigo(codigo);
        Optional<Usuario> possivelUsuario = usuarioDAO.comEmail(email);

        if (possivelUsuario.isPresent() && possivelToken.isPresent()) {
            TokenDeCadastro token = possivelToken.get();
            Usuario usuario = possivelUsuario.get();

            if (token.getUsuario().equals(usuario)) {
                token.delete();
                usuario.setVerificado(true);
                usuario.update();
                flash("success", "Usuario cadastrado com sucesso!");
                return redirect(routes.UsuarioController.painel()); 
            }

        }

        flash("danger", "Falha na tentativa de cadastro");
        return redirect(routes.UsuarioController.formularioDeNovoUsuario());

    }

Não estou conseguindo achar o erro na minha lógica para retornar ambas as mensagens ao mesmo tempo.

18 respostas

Daniel, pode compartilhar o código inteiro do seu controller? Desde a declaração de pacote até o final, mesmo. E também o arquivo de rotas?

Meu chute é que você indicou que esse controller precisa de autenticação, então ele falha mas ao mesmo tempo redirecionando pra rota certa.

Ou então pode ser que tenha algum problema no cadastro de rotas. Vou verificar isso assim que você compartilhar esses códigos.

Olá Marco, segue a classe completa e o arquivo de rotas:

package controllers;

import java.util.Optional;
import org.mindrot.jbcrypt.BCrypt;
import com.google.inject.Inject;
import autenticadores.UsuarioAutenticado;
import daos.TokenDeCadastroDAO;
import daos.UsuarioDAO;
import models.EmailDeCadastro;
import models.TokenDeCadastro;
import models.Usuario;
import play.api.libs.mailer.MailerClient;
import play.data.DynamicForm;
import play.data.Form;
import play.data.FormFactory;
import play.mvc.*;
import play.mvc.Security.Authenticated;
import validadores.ValidadorDeUsuario;
import views.html.*;

public class UsuarioController extends Controller {

    public static final String AUTH = "auth";

    @Inject
    FormFactory formularios;
    @Inject
    private ValidadorDeUsuario validadorDeUsuario;
    @Inject
    private MailerClient enviador;
    @Inject
    private TokenDeCadastroDAO tokenDeCadastroDAO;
    @Inject
    private UsuarioDAO usuarioDAO;

    public Result formularioDeNovoUsuario() {
        play.data.Form<Usuario> formulario = formularios.form(Usuario.class);
        return ok(formularioDeNovoUsuario.render(formulario));
    }

    public Result salvaNovoUsuario() {
        Form<Usuario> formulario = formularios.form(Usuario.class).bindFromRequest();

        if (validadorDeUsuario.temErros(formulario)) {
            flash("danger", "Existem erros no preenchimento do cadastro");
            return badRequest(formularioDeNovoUsuario.render(formulario));
        }

        Usuario usuario = formulario.get();
        String criptoSenha = BCrypt.hashpw(usuario.getSenha(), BCrypt.gensalt());
        usuario.setSenha(criptoSenha);
        usuario.save();
        TokenDeCadastro token = new TokenDeCadastro(usuario);
        token.save();
        enviador.send(new EmailDeCadastro(token));
        flash("success", "Um email foi enviado para confirmar seu cadastro!");
        return redirect(routes.UsuarioController.formularioDeNovoUsuario()); 
    }

    public Result confirmaCadastro(String email, String codigo) {
        Optional<TokenDeCadastro> possivelToken = tokenDeCadastroDAO.comCodigo(codigo);
        Optional<Usuario> possivelUsuario = usuarioDAO.comEmail(email);

        if (possivelUsuario.isPresent() && possivelToken.isPresent()) {
            TokenDeCadastro token = possivelToken.get();
            Usuario usuario = possivelUsuario.get();

            if (token.getUsuario().equals(usuario)) {
                token.delete();
                usuario.setVerificado(true);
                usuario.update();
                flash("success", "Usuario cadastrado com sucesso!");
                return redirect(routes.UsuarioController.painel()); 
            }

        }

        flash("danger", "Falha na tentativa de cadastro");
        return redirect(routes.UsuarioController.formularioDeNovoUsuario());

    }

    @Authenticated(UsuarioAutenticado.class)
    public Result painel() {
        return ok(painel.render());
    }

    public Result formularioDeLogin() {
        return ok(formularioDeLogin.render(formularios.form()));
    }


    public Result fazLogin() {
        DynamicForm formulario = formularios.form().bindFromRequest();
        String email = formulario.get("email");
        String senhaCriptografada = BCrypt.hashpw(formulario.get("senha"), BCrypt.gensalt());
        Optional<Usuario> possivelUsuario = usuarioDAO.comEmailESenha(email, senhaCriptografada);

        if (possivelUsuario.isPresent()) {
            Usuario usuario = possivelUsuario.get();

            if (usuario.isVerificado()) {
                session(AUTH, usuario.getEmail());
                flash("success", "Login efetuado com sucesso!");
                return redirect(routes.UsuarioController.painel());
            } else {
                flash("warning", "Usuario não confirmado, verfique seu email.");
            }
        } else {
            flash("danger", "Credenciais inválidas");
        }

        return redirect(routes.UsuarioController.formularioDeLogin());
    }

    @Authenticated(UsuarioAutenticado.class)
    public Result fazLogout(){
        session().clear();
        flash("success", "Logout efetuado com sucesso!");
        return redirect(routes.UsuarioController.formularioDeLogin());
    }

}

rotas:

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

#Rotas de cadastro
GET  /produto/novo                    controllers.ProdutoController.formularioDeNovoProduto    
POST /produto/novo                  controllers.ProdutoController.salvaNovoProduto
GET  /usuario/novo                  controllers.UsuarioController.formularioDeNovoUsuario
POST /usuario/novo                  controllers.UsuarioController.salvaNovoUsuario

#Rotas de busca
GET  /api/produto                   controllers.ApiController.comFiltros
GET  /api/produtos/todos            controllers.ApiController.todosOsProdutos    
GET  /api/produtos/tipo/:tipo        controllers.ApiController.doTipo(tipo)

#Rota de confirmacao
GET /usuario/confirma/:email/:codigo controllers.UsuarioController.confirmaCadastro(email, codigo)

#Rotas de login
GET  /login                           controllers.UsuarioController.formularioDeLogin    
POST /login                        controllers.UsuarioController.fazLogin
GET  /usuario/painel               controllers.UsuarioController.painel
GET  /logout                       controllers.UsuarioController.fazLogout

GET  /assets/*file                  controllers.Assets.versioned(path="/public", file: Asset)

Ah! achei o problema, Daniel! ao confirmar o cadastro, você deve logar automaticamente o usuário inserindo ele na sessão. ou então alterar o redirecionamento.

Como ele redireciona para o painel, sem o usuário estar logado, aparece a mensagem de usuário não autorizado.

Eu fiz uma alteração no confirmaCadastro redirecionando direto para fazLogin(), ele faz a confirmação mas quando tento logar ele retorna uma mensagem de "Credenciais invalidas".

Ah sim, faz sentido. Pensa comigo: o método fazLogin é para o qual vc envia o formulário de login. Portanto, ele recebe um usuário e uma senha. Porém, o método confirmaCadastro não recebeu nenhum parâmetro de senha.

Faça o redirecionamento para o método formularioDeLogin para que o usuário recém criado possa digitar suas credenciais corretamente e tudo vai dar certo =)

E ai Daniel, conseguiu resolver o problema? Espero que sim, fico no aguardo da sua resposta!

Bom dia Marco, fiz a alteração como você disse, já havia pensado nisso mas ele retornava um erro de rota não encontrada e só funcionou após eu remover a mensagem em flash, contudo o erro ainda persiste. Eu consultei a documentação do play! procurando uma forma de debugar esse código para tentar achar o erro porém o procedimento indicado retorna uma mensagem de deprecated. Existe alguma forma de debugar esse código para tentar ver passo a passo o que ocorre e descobrir onde está o problema?

Debugar o servidor Play! não é trivial mas dá pra fazer. Dá uma olhada aqui na documentação, logo abaixo da próxima imagem. Se você souber utilizar a IDE Intellij-Idea, utilizá-la é bem mais fácil, especialmente para isso.

Quanto ao seu problema, faça um teste? Troque seu código por esse aqui:

if (token.getUsuario().equals(usuario)) {
  token.delete();
  usuario.setVerificado(true);
  usuario.update();
  flash("success", "Usuario cadastrado com sucesso!");
  return redirect("/login"); 
}

E se der algum erro, compartilhe o stack-trace inteiro?

Se não der, troque de volta por return redirect(routes.UsuarioController.formularioDeLogin()); e compartilhe o stack-trace completo do erro que dá desse jeito?

Ah, outra coisa: pra compartilhar código ou logs, use o botão no canto do campo de postagem: <> inserir código que facilita bastante pra visualizar!

Com a mensagem em flash nas primeiras vezes ele retornou um Action Not Found For request 'GET /usuario/confirma/email@gmail.com/$2a$10$wWBN/gcKLkAEb57chiM12eWbtvIno/kr7hiowItq/hgXgdZ0/37DK'

Eu reescrevi a mensagem flash e ele foi redirecionado para o formulário de login exatamente como deveria ser.

Aparentemente ele não cai nesse segundo if, eu verifiquei minha tabela token_de_cadastro e ela está armazenando o token de cada tentativa já que ele não passa pelo token.delete(). Ele não apresenta nenhum erro além da rota não encontrada que mencionei anteriormente. O que eu não estou entendendo é por que ele está fazendo o redirecionamento sendo que ele não passa na segunda condição ao invés de retornar para o formulário de novo usuário.

Erro meu, coloquei um system.out depois do segundo condicional e o validador de usuário realmente está passando por ele, o que parecer não estar sendo executado é:

token.delete(); usuario.setVerificado(true); usuario.update();

o valor da coluna verificado da tabela de usuários é setada com valor 0, mesmo mudando diretamente no banco o problema continua.

solução!

Daniel, repare na URL gerada.

Eu recomendo o uso do Crypt.sha1 pois ele não gera um hash com barras (/). A barra é caractere reservado na hora de reconhecer URLs, então esse hash aí está alterando a URL final pra uma rota não reconhecida.

Duas opções que devem resolver:

  • Fazer um encoding da URL antes de inserir no email
  • Alterar a rota para não aceitar parâmetros inline e sim do modo clássico:
    /usuario/confirma?email=email@gmail.com&token=$2a$10$wWBN/gcKLkAEb57chiM12eWbtvIno/kr7hiowItq/hgXgdZ0/37DK
    Alguma das duas deeeve resolver esse problema.

Alterar o tipo de criptografia resolveu o problema da rota. Existe a possibilidade de o tipo criptografia que eu usei, JBCrypt no caso, esteja impedindo o login?

Não deveriam pois o formulário de login envia os dados ainda não criptografados. A criptografia ocorre no controller, então não deveria ter problema. Só se o salt() estiver variando de uma chamada para outra!

Só vou dar uma resumida aqui no processo de solução do tópico:

  1. A confirmação de cadastro redirecionava para o painel sem fazer auto-login do usuário, causando um redirecionamento pra página de login com um segundo alerta 401. Solução: redirecionar para página de login ou fazer auto-login.
  2. URL com token de confirmação de cadastro dava rota inexistente. Solução: a criptografia usada gerava um hash com /, confundindo o roteamento. Trocar o sistema de criptografia ou usar parâmetros dinâmicos (/rota?param1&param2).

Acho que encontrei o erro, no método de login ele criptografa a senha para comparar com a que foi salva,mas como nunca é gerado o mesmo hash ele cai no else e retorna a mensagem de credenciais inválidas. Estou tentando descobrir como resolver isso.

String senhaCriptografada = BCrypt.hashpw(formulario.get("senha"), BCrypt.gensalt());

Esse gensalt() retorna coisas aleatórias diferentes a cada execução. Troca isso por um hash em string mesmo, tipo "o-sal-do-meu-sistema". Claro que o ideal é que o sal em si seja bem complexo pra que seja quase impossível descobri-lo.

O salt() não retorna um erro quando se passa uma String já que ele não consegue fazer o hash, a solução provisória que encontrei (que é bem tosca diga-se de passagem) foi só criptografar a senha após o login para ele ter os mesmos valores do banco e depois criptografar, porém isso só funciona uma vez já que depois disso volto ao mesmo caso de antes.

Estou ficando confuso e o tópico ficando muito grande. Pode abrir uma nova questão sobre esse assunto da criptografia, já com exemplo de código e resultados?

Daniel, esse tópico foi solucionado? Se sim, lembre-se de marcar alguma das respostas como solução! Isso ajuda também os outros alunos com dúvidas semelhantes, pois agiliza na hora de visualizar a solução do problema.

=) Bons estudos!