Spring Boot Aplicacion Web Parte 8.

Una parte importante cuando se maneja usuarios es poder actualizar sus credenciales (password). No es recomendable ponerlo en el formulario normal para no tener que actualizar el password cada vez que quieras actualizar cualquier informacion del usuario.

Algo recomendado es que obligar al usuario a ingresar su password actual para poder actualizarlo a uno nuevo. Cuando agreguemos Roles dejaremos que el Admin le cambie el password a cualquier usuario con rol User, empezamos.

En esta parte utilizaremos AJAX, para que aprendas como hacer llamados asíncronos utilizando JSON.

Contenido

  1. Crear Controlador y POJO.
  2. Crear Formulario Popup (HTML).
  3. Crear Servicio.
  4. Retornar JSON Response.
  5. Commit Git.
  6. Video Paso a Paso.

1.Crear Controlador y POJO

Crearemos un Java POJO que contendra el id del usuario y el actual y nuevo password, ademas tendremos que retornar ese nuevo pojo en los controladores que tengamos para edicion de usuario.

ChangePasswordForm.java

package com.cristianruizblog.springbootApp.dto;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class ChangePasswordForm {
@NotNullprivate Long id;
@NotBlank(message="Current Password must not be blank")
private String currentPassword;

@NotBlank(message="New Password must not be blank")
private String newPassword;

@NotBlank(message="Confirm Password must not be blank")
private String confirmPassword;

public ChangePasswordForm() { }
public ChangePasswordForm(Long id) {this.id = id;}

//Getters & Setters
}

UserController.java

Busca los siguientes metodos y agrega la liena del atributo “passwordForm”

@GetMapping("/editUser/{id}")public String getEditUserForm(Model model, @PathVariable(name="id") Long id) {
....
model.addAttribute("passwordForm",new ChangePasswordForm(user.getId()));
}

@PostMapping("/editUser")
	public String postEditUserForm(@Valid @ModelAttribute("userForm")User user, BindingResult result, ModelMap model) {
		try {
                  ... 
		} catch (Exception e) {
                  ...
                  model.addAttribute("passwordForm",new ChangePasswordForm(user.getId()));
                   ...
		} 
   ...
}		

2.Crear Formulario Popup

Crea un nuevo archivo “change-password.html” en la carpeta user-form.

<div class="modal fade" id="changePasswordModal" tabindex="-1" role="dialog" aria-labelledby="changePasswordModalTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="changePasswordLongModal">Change user password</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <form th:action="@{/editUser/changePassword}"
				id="changePasswordForm" th:object="${passwordForm}" method="post" class="form" role="form">
			<input class="form-control" type="hidden" th:field="${passwordForm.id}">
			<div class="form-group row">
				<label class="col-lg-3 col-form-label 	form-control-label">Current Password</label>
				<div class="col-lg-9">
					<input class="form-control" type="password" th:field="${passwordForm.currentPassword}">
					<div class="alert-danger" th:if="${#fields.hasErrors('currentPassword')}" th:errors="*{currentPassword}">Password</div>
				</div>
			</div>
			<div class="form-group row">
				<label class="col-lg-3 col-form-label 	form-control-label">New Password</label>
				<div class="col-lg-9">
					<input class="form-control" type="password" th:field="${passwordForm.newPassword}">
					<div class="alert-danger" th:if="${#fields.hasErrors('newPassword')}" th:errors="*{newPassword}">Password</div>
				</div>
			</div>
			<div class="form-group row">
				<label class="col-lg-3 col-form-label form-control-label">Confirm Password</label>
				<div class="col-lg-9">
					<input class="form-control" type="password" th:field="${passwordForm.confirmPassword}">
					<div class="alert-danger" th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}">Confirm Password</div>
				</div>
			</div>
			<div class="col-lg-12">
				<div class="alert alert-danger d-none text-center" id="changePasswordError">Change Password Error</div>
			</div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        <button type="button" onClick="submitChangePassword()" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

<script type="text/javascript">
function submitChangePassword(){
	var params = {};
	params["id"] = $("#id").val();
	params["currentPassword"] = $("#currentPassword").val();
	params["newPassword"] = $("#newPassword").val();
	params["confirmPassword"] = $("#confirmPassword").val();
	
	$.ajax({
        type: "POST",
        contentType: "application/json",
        url: "/editUser/changePassword",
        data: JSON.stringify(params),
        dataType: 'text',
        cache: false,
        timeout: 600000,
        success: function (data) {
        	$("#changePasswordForm")[0].reset();
        	
        	$("#changePasswordError").addClass( "d-none" );
        	$("#formSuccess").removeClass( "d-none" );
            $("#formSuccess").html("Password Changed successfully!.");

            $('#changePasswordModal').modal('toggle');
            setTimeout(function(){	$("#formSuccess").hide('slow'); }, 2000);
        },
        error: function (e) {
            $("#changePasswordError").removeClass( "d-none" );
            $("#changePasswordError").html(e.responseText);
        }
    });

}
</script>

user-view.html

...
<div th:if="${editMode}" th:include="user-form/change-password.html" th:remove="tag"></div>
<div th:include="user-form/confirm-delete-modal.html" th:remove="tag"></div>
</body>

user-form.html

En la seccion de los botones al final del archivo, pon la siguiente linea entre el boton de cancelar y guardar

<button type="button" class="btn btn-secondary" data-toggle="modal" th:if="${editMode}" data-target="#changePasswordModal">Change Password</button>

Inicial tu aplicacion y selecciona un usuario para editar, click en el boton de cambiar password y deberias de tener lo siguient

Change Password Screen
Change Password Screen

3.Crear Servicio.

UserService.java

...
public User changePassword(ChangePasswordForm form) throws Exception;

UserServiceImpl.java

...
public User changePassword(ChangePasswordForm form) throws Exception{
		User storedUser = userRepository
				.findById( form.getId() )
				.orElseThrow(() -> new Exception("UsernotFound in ChangePassword -"+this.getClass().getName()));
		
		if( form.getCurrentPassword().equals(storedUser.getPassword())) {
			throw new Exception("Current Password Incorrect.");
		}
		
		if ( form.getCurrentPassword().equals(form.getNewPassword())) {
			throw new Exception("New Password must be different than Current Password!");
		}
		
		if( !form.getNewPassword().equals(form.getConfirmPassword())) {
			throw new Exception("New Password and Confirm Password does not match!");
		}
		
		storedUser.setPassword(form.getNewPassword());
		return userRepository.save(storedUser);
	}

4.Retornar JSON Response.

Para poder entregar una respuesta asíncrona, utilizaremos como objeto de retorno ResponseEntity<?>, al cual le podremos asignar codigo http de respuesta y un mensaje.

Ademas recibiremos el formulario con la anotacion @RequestBody, indicando que la estructura del objeto ChangePasswordForm esta dentro de request.

Si ocurre algun error cambiando el password o si tiene algun error identificado por la anotacion @Valid, lo concatenamos en un string y lo resgresamos en el cuerpo de un bad request

UserController.java


@PostMapping("/editUser/changePassword")
	public ResponseEntity postEditUseChangePassword(@Valid @RequestBody ChangePasswordForm form, Errors errors) {
		try {
			//If error, just return a 400 bad request, along with the error message
	        if (errors.hasErrors()) {
	            String result = errors.getAllErrors()
	                        .stream().map(x -> x.getDefaultMessage())
	                        .collect(Collectors.joining("
")); throw new Exception(result); } userService.changePassword(form); } catch (Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } return ResponseEntity.ok("success"); }

5.Git Commit

Change Password Git Commit
Change Password Git Commit

Una vez tengas todo andando y funcionando, cambia la clave de un usuario y verifica en la base de datos que se haya cambiado correctamente.

Github commit:
https://github.com/cruizg93/Spring-Boot-Aplicacion/commit/afbf614cf1860f8405ba4e43a62909c4d9745257

6.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 8.”

  1. Hola tengo este error ya valide los nombres en el modal y en el controlador
    status”:403,”error”:”Forbidden”,”message”:”Forbidden”,”path”:”/editUser/changePassword”}
    $.ajax({
    type: “POST”,
    contentType: “application/json”,
    url: “/editUser/changePassword”,
    data: JSON.stringify(params),

    Controlador
    @PostMapping(“/editUser/changePassword”)
    public ResponseEntity postEditUseChangePassword(@Valid @RequestBody ChangePasswordForm form, Errors errors) {
    try {

    1. El error 403, es un error de seguridad, significa que tu no tienes acceso a esa url, puede ser por permiso de roles o porque tu usuario no es valido en el contexto de seguridad. Intenta cerrar sesión e intenta volver a cambiar la contraseña.

      Si compartes tu codigo podria darle una mirada.

Deja un comentario

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