domingo, 12 de julho de 2020

Contas Pagar/Receber - Parte XV - Login e Autenticação

Introdução
Nesse décimo quinto post vou mostrar como criar o cadastro de usuÔrio da aplicação e configurar a autenticação. Vou criar um CRUD de usuÔrio e depois vou mostrar como fazer a autenticação pela tela tela de login. Vou mostrar também como configurar a senha no primeiro acesso, alteração de senha e criar o primeiro usuÔrio da aplicação "admin".

Cadastro UsuƔrio
O CRUD de cadastro de usuÔrio serÔ bem simples, somente contendo o nome, email, cargo, login, senha e último acesso. Então agora vou criar o CRUD com a seguinte definição no package-info.
package-info.java:

@JArchGenerateLogicCrud(nameSubPackage = "usuario",
master = @JArchGenerateMaster(name = "UsuƔrio", tableName = "tb_usuario",
fields = {
@JArchGenerateField(fieldName = "nome", fieldTable = "nm_usuario", description = "Nome",
type = FieldType.NAME, descriptionLookup = true, required = true,
search = @JArchGenerateSearch(row = 1, column = 1, span = 6),
xhtml = @JArchGenerateXhtml(rowDataXhtml = 1, columnDataXhtml = 1, showDataTableList = true)),
@JArchGenerateField(fieldName = "email", fieldTable = "ee_usuario", description = "E-mail",
type = FieldType.EMAIL, required = true, search = {},
xhtml = @JArchGenerateXhtml(rowDataXhtml = 2, columnDataXhtml = 1, showDataTableList = false)),
@JArchGenerateField(fieldName = "cargo", fieldTable = "tp_cargo", description = "Cargo",
type = FieldType.TYPE, required = true,
search = @JArchGenerateSearch(row = 1, column = 2, span = 3),
xhtml = @JArchGenerateXhtml(rowDataXhtml = 3, columnDataXhtml = 1, showDataTableList = true)),
@JArchGenerateField(fieldName = "login", fieldTable = "nm_login", description = "Login",
type = FieldType.SHORT_NAME, required = true, codeLookup = true, exclusive = true,
search = @JArchGenerateSearch(row = 1, column = 3, span = 3),
xhtml = @JArchGenerateXhtml(rowDataXhtml = 4, columnDataXhtml = 1, showDataTableList = true)),
@JArchGenerateField(fieldName = "senha", fieldTable = "cn_senha", description = "Senha",
type = FieldType.PASSWORD, search = {}, xhtml = {}),
@JArchGenerateField(fieldName = "ultimoAcesso", fieldTable = "dh_ultimoacesso", description = "Último Acesso",
type = FieldType.DATE_TIME, search = {}, xhtml = {})
}
)
)

Agora vou executar os steps CLEAN, COMPILE E PACKAGE do MAVEN. Após a criação dos artefatos vou alterar o UsuarioEntity para implementar a interface IUser.
UsuarioEntity.java:

@JArchLookup(codeAttribute = "login", descriptionAttribute = "nome")
@Audited
@Table(name = "tb_usuario",
indexes = {
@Index(columnList = "nm_usuario", name = "dx_usuarionmusu"),
@Index(columnList = "tp_cargo", name = "dx_usuariotpcar"),
@Index(columnList = "nm_login", name = "dx_usuarionmlog")
})
@Entity(name = "usuario")
@SequenceGenerator(name = "UsuarioIdSequence", sequenceName = "sq_idusuario", allocationSize = 1)
public class UsuarioEntity extends CrudMultiTenantEntity implements IUser {

E tambĆ©m ajustar o tipo do atributo ultimoAcesso de LocalDateTime para Date e remover a anotação de converter do JPA. 

@Column(name = "dh_ultimoacesso")
private Date ultimoAcesso;

E ajustar o getter e setter desse atributo:

public Date getUltimoAcesso() {
return ultimoAcesso;
}

public void setUltimoAcesso(Date ultimoAcesso) {
this.ultimoAcesso = ultimoAcesso;
}

Agora vou ajustar o enumerado de cargo colocando os seguintes valores: Administrador, Diretor, Gerente, Supervisor e Operador.
CargoType.java:
public enum CargoType {
OPERADOR("OPR", "label.operador"),
SUPERVISOR("SPR", "label.supervisor"),
GERENTE("GRN", "label.gerente"),
DIRETOR("DRT", "label.diretor"),
ADMINISTRADOR("ADM", "label.administrador");

private final String abbreviation;
private final String description;

CargoType(String abbreviation, String description) {
this.abbreviation = abbreviation;
this.description = description;
}

public String getAbbreviation() {
return abbreviation;
}

public String getDescription() {
return BundleUtils.messageBundle(description);
}

public static CargoType abbreviationToEnum(String abbreviation){
return Arrays.stream(values()).filter(m -> m.getAbbreviation().equals(abbreviation)).findAny().orElse(null);
}

public static Collection<CargoType> getCollection() {
return Arrays.stream(values()).collect(Collectors.toCollection(ArrayList::new));
}
}
Vou atualizar o bundle com os dados do cargo.
bundle_pt_BR.properties:

label.operador=Operador
label.supervisor=Supervisor
label.gerente=Gerente
label.diretor=Diretor
label.administrador=Administrador

Vou adicionar no menu dentro do cadastro a opção UsuÔrio logo após a Pessoa.
MenuAction.java
.addSubMenu(MenuBuilder
.newInstance()
.name(BundleUtils.messageBundle("label.pessoa"))
.action("../pessoa/pessoaList.jsf")
.build())
.addSubMenu(MenuBuilder
.newInstance()
.name(BundleUtils.messageBundle("label.usuario"))
.action("../usuario/usuarioList.jsf")
.build())
.build());

Publicando novamente temos a tela de lista:

Tela de dados:

Autenticação
Agora que tem o cadastro de usuÔrio, vou criar os métodos na fachada para criar o usuÔrio admin, fazer a pesquisa pelo login e atualizar último acesso. O usuÔrio admin serÔ criado quando não existir nenhum usuÔrio cadastrado (primeiro acesso ao sistema daquele tenant).
UsuarioFacade.java:

public class UsuarioFacade extends CrudFacade<UsuarioEntity, IUsuarioManager> {

public void criaUsuarioAdmin() {
if (count() > 0) {
return;
}

getUserInformation().set(UserSystem.get());
UsuarioEntity usuarioAdmin = createEntity();
usuarioAdmin.setNome("Administrador");
usuarioAdmin.setEmail("admin@admin.com.br");
usuarioAdmin.setCargo(CargoType.ADMINISTRADOR);
usuarioAdmin.setLogin("admin");
usuarioAdmin.setSenha(Md5Utils.generate("admin"));
insert(usuarioAdmin);
}

public Optional<UsuarioEntity> pesquisaLogin(String login) {
return searchAny(UsuarioEntity_.login, login);
}

public void atualizaUltimoAcesso(UsuarioEntity usuario) {
usuario.setUltimoAcesso(new Date());
change(usuario);
}
}
E na criação da sessĆ£o do usuĆ”rio vou fazer chamada do mĆ©todo criaUsuarioAdmin() na Ćŗltima linha do sessionCreated.
SessionListener.java:

@WebListener
public class SessionListener implements HttpSessionListener {

@Inject
private TenantFacade tenantFacade;

@Inject
private AlertaFacade alertaFacade;

@Inject
private MensagemFacade mensagemFacade;

@Inject
private UsuarioFacade usuarioFacade;

@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
tenantFacade.configTenant(JsfUtils.getServerName());
alertaFacade.atualizaAlertaSessao();
mensagemFacade.atualizaMensagemSessao();
usuarioFacade.criaUsuarioAdmin();
}

@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
//
}
}
Agora vou alterar o LoginAction para fazer a validação dentro do método processLogin().
LoginAction.java:

@JArchViewScoped
public class LoginAction extends BaseLoginAction {

@Inject
private UsuarioFacade usuarioFachada;

private UsuarioEntity usuario;

@Override
public IUser processLogin() throws LoginException {
if (getLogin() == null || getLogin().isEmpty()) {
throw new LoginException(BundleUtils.messageBundle("message.loginPreenchimentoObrigatorio"));
}

Optional<UsuarioEntity> usuarioOptional = usuarioFachada.pesquisaLogin(getLogin());

if (!usuarioOptional.isPresent()) {
throw new LoginException(BundleUtils.messageBundle("message.loginInvalido"));
}

usuario = usuarioOptional.get();

if (usuario.getSenha() == null) {
getUserInformation().set(usuario);
JsfUtils.redirect("alterarSenha.jsf");
} else if (getPassword() == null || getPassword().isEmpty()) {
throw new LoginException(BundleUtils.messageBundle("message.senhaPreenchimentoObrigatorio"));
}

if (!Md5Utils.generate(getPassword()).equals(usuario.getSenha())) {
throw new LoginException(BundleUtils.messageBundle("message.senhaInvalida"));
}

return usuario;
}

@Override
public void processAfterLogin() {
usuarioFachada.atualizaUltimoAcesso(usuario);
}

@Override
public void forgotPassword(String login) {
}
}
Repare que estĆ” sendo feito uma verificação que se a senha nĆ£o foi cadastrada (usuario.getSenha() == null) entĆ£o redireciona para a tela de alterarSenha.jsf. EntĆ£o vou criar o AlteraSenhaAction extendendo do BaseAlteraSenhaAction para gravar a senha. 
AlteraSenhaAction.java:

@JArchViewScoped
public class AlteraSenhaAction extends BaseAlteraSenhaAction {

@Inject
private UsuarioFacade usuarioFacade;

private UsuarioEntity usuario;

@Override
public void validNewPassword(String login, String senhaAtual, String novaSenha, String confirmaSenha) {
usuario = usuarioFacade.searchUniqueFilter(UsuarioEntity_.login, login);

if (usuario.getSenha() != null && !Md5Utils.generate(senhaAtual).equals(usuario.getSenha())) {
throw new LoginException(BundleUtils.messageBundle("message.senhaAtualInvalida"));
}

if (!getNovaSenha().equals(getConfirmaSenha())) {
throw new LoginException(BundleUtils.messageBundle("message.novaSenhaConfirmacaoNaoConferem"));
}
}

@Override
public void updatePassword(String login, String novaSenha) {
usuario.setSenha(Md5Utils.generate(novaSenha));
usuarioFacade.change(usuario);
}
}
Atualizo tambƩm o bundle.
bundle_pt_BR.properties:

message.loginPreenchimentoObrigatorio=Login \u00E9 de preenchimento obrigat\u00F3rio
message.loginInvalido=Login inv\u00E1lido
message.senhaPreenchimentoObrigatorio=Senha \u00E9 de preenchimento obrigat\u00F3rio
message.senhaInvalida=Senha inv\u00E1lida message.senhaAtualInvalida
=Senha atual inv\u00E1lida
message.novaSenhaConfirmacaoNaoConferem=Nova senha e confirma\u00E7\u00E3o senha n\u00E3o conferem

Republicando a aplicação e tentando fazer o login com qualquer usuÔrio:

Então faço o login com o usuÔrio admin e senha admin (que foi criando automaticamente pela aplicação).

Vou cadastrar o usuƔrio wagner.araujo:

Vou logar como wagner.araujo e fazer login pela primeira vez, note que serÔ redirecionado para a tela de alteração de senha (para que cadastre a senha):

SerÔ redirecionado para alteração de senha:

E cadastrando a senha:

Agora pra finalizar vou criar uma opção no menu para que o usuÔrio possa alterar a senha:
MenuAction.java:

menu.add(MenuBuilder
.newInstance()
.name(BundleUtils.messageBundle("label.alterarSenha"))
.action("../login/alterarSenha.jsf")
.build());

Agora no menu tem a opção de alteração de senha:

Conclusão
Nesse post mostrei como criar o cadastro de usuÔrio, configurar a senha no primeiro acesso, criar o usuÔrio inicial admin e alteração de senha. Esse é um controle de acesso bem simples, não quis entrar nos detalhes de criação de perfil, menus, etc. Essas informações serão suficientes para os próximos posts.

Segue o link dessa video aula: https://youtu.be/UUmkJBjX8kI

AtƩ mais,

Nenhum comentƔrio:

Postar um comentƔrio

Versão 23.3.0-Final

      Introdução Nesse post vou mostrar as principais novidades da versĆ£o 23.3.0, algumas correƧƵes e pequenas alteraƧƵes. AlteraƧƵes AlĆ©m d...