Spring Boot Aplicacion Web Parte 11 (BONUS)

Ok, nuestra aplicacion esta terminada, pero como cualquier otro sistema hay que hacer retoques. Me encargue de recopilar las mejoras y arreglos de los comentarios de los videos en Youtube y de cada post.

1# Fix

Cuando agregamos spring security encriptamos los passwords/contraseñas. Pero solo lo hicimos cuando estamos actualizando dicha contraseña, por lo tanto vamos a forzar los usuarios nuevos a encriptar tambien la contraseña.

UserServiceImpl.java


//Asegurate de tener este autowired al inicio de la clase
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;

//Busca tu metodo crear usuario e implementa la encriptacion del password como se muestra abajo.
@Override
public User createUser(User user) throws Exception {
	if (checkUsernameAvailable(user) && checkPasswordValid(user)) {
		String encodedPassword = bCryptPasswordEncoder.encode(user.getPassword());
		user.setPassword(encodedPassword);
		user = repository.save(user);
	}
	return user;
}

2# Fix

Cuando verificamos el uso de roles o acceso del usuario en nuestro backend habia un error y practicamente estaba omitiendo la validacion del ADMIN que aplicamos en isLoggedUserADMIN en UserServiceImpl.java.

Esta es la manera correcta de verificar si un usuario contiene el rol admin dentro de sus permisos.

UserServiceImpl.java


private boolean isLoggedUserADMIN() {
	//Obtener el usuario logeado
	Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
	
	UserDetails loggedUser = null;
	Object roles = null;

	//Verificar que ese objeto traido de sesion es el usuario
	if (principal instanceof UserDetails) {
		loggedUser = (UserDetails) principal;

		roles = loggedUser.getAuthorities().stream()
				.filter(x -> "ROLE_ADMIN".equals(x.getAuthority())).findFirst()
				.orElse(null); 
	}
	return roles != null ? true : false;
}

Como bonus en este fix te mostrare como obtener el usuario logeado desde la sesion y transformado en tu propia entidad para poder usarlo en tu aplicacion.

private User getLoggedUser() throws Exception {
	//Obtener el usuario logeado
	Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
	
	UserDetails loggedUser = null;

	//Verificar que ese objeto traido de sesion es el usuario
	if (principal instanceof UserDetails) {
		loggedUser = (UserDetails) principal;
	}
	
	User myUser = repository
			.findByUsername(loggedUser.getUsername()).orElseThrow(() -> new Exception(""));
	
	return myUser;
}

3# Fix

Algo que no interprete temprano fue que al editar estaba pasando el password del usuario encriptado en el formulario como se puede notar en la imagen.

Encrypted Password on HTML
Encrypted Password on HTML

La manera de resolver esto es abrir tu archivo user-form.html y reemplazar la linea de password por la siguiente:
git commit para este cambio:
https://github.com/cruizg93/Spring-Boot-Aplicacion/commit/a9611394cc46a0699d2ec019b1caa39147d53fb6

<input class="form-control" type=hidden name="password" value="xxxx" th:if="${editMode}">

4# Fix

De la manera en que estamos manejando la validación manual de algunos campos de texto es mostrando el mensaje de error en un espacio fijo al final de la pagina, como si fuera un error general.

Pero durante este proceso descubrí como insertar mi error manual dentro del Binding Result para mostrar mi mensaje como lo hace la validacion con anotaciones, dejame mostrarte a que me refiero.

La imagen de la izquierda es como lo tenemos actualmente y la de la derecha es como quedara despues de este FIX.

Vamos a crear una excepción para manejar este escenario.

package com.cristianRuizBlog.aplicacion.Exception;

public class CustomeFieldValidationException extends Exception{

	private static final long serialVersionUID = -4995433707591853255L;

	private String fieldName;
	
	public CustomeFieldValidationException(String message, String fieldName) {
		super(message);
		this.fieldName = fieldName;
	}
	
	public String getFieldName() {
		return fieldName;
	}
}

Ahora tienes que buscar los siguientes métodos y reemplazar la excepción general por la que creamos anteriormente, la diferencia es que tienes que agregar como segundo parámetro el nombre del campo.

private boolean checkUsernameAvailable(User user) throws Exception {
	Optional<User> userFound = repository.findByUsername(user.getUsername());
	if (userFound.isPresent()) {
		throw new CustomeFieldValidationException("Username no disponible","username");
	}
	return true;
}

private boolean checkPasswordValid(User user) throws Exception {
	if (user.getConfirmPassword() == null || user.getConfirmPassword().isEmpty()) {
		throw new CustomeFieldValidationException("Confirm Password es obligatorio","confirmPassword");
	}
	
	if ( !user.getPassword().equals(user.getConfirmPassword())) {
		throw new CustomeFieldValidationException("Password y Confirm Password no son iguales","password");
	}
	return true;
}

Para finalizar solo necesitas capturar la nueva excepcion en el controlador de la siguiente manera en la linea 12.

@PostMapping("/userForm")
public String createUser(@Valid @ModelAttribute("userForm")User user, BindingResult result, ModelMap model) {
	if(result.hasErrors()) {
		model.addAttribute("userForm", user);
		model.addAttribute("formTab","active");
	}else {
		try {
			userService.createUser(user);
			model.addAttribute("userForm", new User());
			model.addAttribute("listTab","active");
			
		}catch (CustomeFieldValidationException cfve) {
			result.rejectValue(cfve.getFieldName(), null, cfve.getMessage());
			model.addAttribute("userForm", user);
			model.addAttribute("formTab","active");
			model.addAttribute("userList", userService.getAllUsers());
			model.addAttribute("roles",roleRepository.findAll());
		}
		catch (Exception e) {
			model.addAttribute("formErrorMessage",e.getMessage());
			model.addAttribute("userForm", user);
			model.addAttribute("formTab","active");
			model.addAttribute("userList", userService.getAllUsers());
			model.addAttribute("roles",roleRepository.findAll());
		}
	}
	
	model.addAttribute("userList", userService.getAllUsers());
	model.addAttribute("roles",roleRepository.findAll());
	return "user-form/user-view";
}

Git COMMIT

Por el momento esto sera todo, no duden en dejar su comentario y dejarme saber que les gustaria ver en los proximos tutoriales.

1) https://github.com/cruizg93/Spring-Boot-Aplicacion/commit/6019754047fbe76a38aa8351d30639a675b88b35
2) https://github.com/cruizg93/Spring-Boot-Aplicacion/commit/e32f295246440274770518ff3c073f24568099ce

Video Paso a Paso

Menu

  1. Setup
  2. Entidades y POJOS
  3. Basic HTML
  4. Lista de Usuarios
  5. Crear Usuario y Validar Campos
  6. Editar Usuario
  7. Eliminar Usuario
  8. Cambiar Contraseña
  9. Spring Security
  10. Paginas de Error
  11. Bonus, Arreglando Cositas
  12. Formulario de registro
  13. Despliegue en Heroku

Gracias por llegar al final de este post.
No se te olvide dejar tus comentario o preguntas aca abajo o en mi twitter @Cruizg93

2 Replies to “Spring Boot Aplicacion Web Parte 11 (BONUS)”

  1. Hola Cristian
    Ante todo, muchísimas gracias por compartir tus conocimientos de una manera tan clara y realista de lo que puede ser la programación. A mí me estás haciendo ver la luz en esto de la programación 🙂
    Estoy terminando de ver tus vídeos y siguiendo el proyecto GitHub veo que me falta el último paso, el ‘Recordar Password’, del cuál estoy también muy interesado en poder implementar en el futuro en alguno de mis proyectos. No sé si al final no hiciste vídeo al respecto o no he visto dónde lo haces. ¿Podrías sacarme de dudas?
    Mil gracias!
    David C.

    1. Hola David, gracias por seguir los videos y me alegra que te hayan servido. No lamentablemente no hice el video de recordar contraseña, me falto, depronto para el siguiente año.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *