Validación de contraseña personalizada de Spring

V

Introducción

En estos días, las políticas de contraseñas son muy comunes y existen en la mayoría de las plataformas en línea. Si bien a ciertos usuarios realmente no les gustan, hay una razón por la que existen: hacer que las contraseñas sean más seguras.

Seguramente ha tenido experiencia con aplicaciones que imponen ciertas reglas para su contraseña, como el número mínimo o máximo de caracteres permitidos, incluidos dígitos, letras mayúsculas, etc.

No importa qué tan bueno sea el sistema de seguridad, si un usuario elige una contraseña débil como “contraseña”, los datos confidenciales pueden quedar expuestos. Si bien algunos usuarios pueden irritarse con las políticas de contraseñas, mantienen los datos del usuario seguros, ya que hacen que los ataques sean mucho más ineficaces.

Para implementar esto en nuestras aplicaciones basadas en Spring, usaremos Passay – una biblioteca creada específicamente para este propósito que facilita la aplicación de políticas de contraseñas en Java.

tenga en cuenta: Este tutorial asume que tiene conocimientos básicos del marco de Spring, por lo que nos centraremos más en Passay por brevedad.

Aparte de las políticas de contraseña, una técnica buena y fundamental para implementar para la seguridad es la codificación de contraseña.

Formulario de inscripción

La forma más sencilla de comenzar con un proyecto esqueleto de Spring Boot, como siempre, es usando Spring Initializr.

Seleccione su versión preferida de Spring Boot y agregue el Web y Thymeleaf dependencias:

Después de esto, generelo como un proyecto de Maven y ¡listo!

Definamos un simple Objeto de transferencia de datos en el que incluiremos todos los atributos que queremos capturar de nuestro formulario:

public class UserDto {

    @NotEmpty
    private String name;

    @Email
    @NotEmpty
    private String email;

    private String password;

Todavía no hemos anotado el campo de contraseña, porque implementaremos una anotación personalizada para esto.

Luego tenemos una clase de controlador simple que sirve el formulario de registro y captura sus datos cuando se envía utilizando el GET/POST asignaciones:

@Controller
@RequestMapping("/signup")
public class SignUpController {

    @ModelAttribute("user")
    public UserDto userDto() {
        return new UserDto();
    }

    @GetMapping
    public String showForm() {
        return "signup";
    }

    @PostMapping
    public String submitForm(@Valid @ModelAttribute("user") UserDto user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "signup";
        }
        return "success";
    }

}

Primero definimos un @ModelAttribute("user") y le asignó un UserDto ejemplo. Este es el objeto que contendrá la información una vez enviada.

Usando este objeto, podemos extraer los datos y conservarlos en la base de datos.

los showForm() El método devuelve un String con el valor “signup”. Dado que tenemos Thymeleaf en nuestra ruta de clase, Spring buscará “signup.html” en la carpeta de plantillas en los recursos.

Del mismo modo, tenemos un submitForm() Mapeo POST que verificará si el formulario tiene algún error. Si lo hace, se redirigirá a la página “signup.html”. De lo contrario, reenviará al usuario a la página de “éxito”.

Thymeleaf es un moderno motor de plantillas Java del lado del servidor para procesar y crear HTML, XML, JavaScript, CSS y texto. Es una alternativa moderna para motores de plantillas más antiguos como Páginas del servidor Java (JSP).

Sigamos adelante y definamos una página “signup.html”:

<form action="#" th:action="@{/signup}" th:object="${user}" method="post">

    <div class="form-group">
        <input type="text" th:field="*{name}" class="form-control"
               id="name" placeholder="Name"> <span
               th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
               class="text-danger"></span>
     </div>
     <div class="form-group">
        <input type="text" th:field="*{email}" class="form-control"
               id="email" placeholder="Email"> <span
               th:if="${#fields.hasErrors('email')}" th:errors="*{email}"
               class="text-danger"></span>
     </div>
     <div class="form-group">
         <input type="text" th:field="*{password}" class="form-control"
                id="password" placeholder="Password">
         <ul class="text-danger" th:each="error: ${#fields.errors('password')}">
             <li th:each="message : ${error.split(',')}">
                 <p class="error-message" th:text="${message}"></p>
             </li>
         </ul>
     </div>

     <div class="col-md-6 mt-5">
         <input type="submit" class="btn btn-primary" value="Submit">
     </div>
</form>

Hay algunas cosas que deben señalarse aquí:

  • th:action = "@{/signup}" – El atributo de acción se refiere a la URL a la que llamamos al enviar el formulario. Nuestro objetivo es la asignación de URL de “registro” en nuestro controlador.
  • method="post" – El atributo de método se refiere al tipo de solicitud que enviamos. Esto tiene que coincidir con el tipo de solicitud definida en el submitForm() método.
  • th:object="${user}" – El atributo de objeto se refiere al nombre del objeto que hemos definido en el controlador anteriormente usando @ModelAttribute("user"). Usando el resto del formulario, completaremos los campos del UserDto instancia y, a continuación, guarde la instancia.

Tenemos otros 3 campos de entrada que están asignados a name, emaily password utilizando th:field etiqueta. Si los campos tienen errores, el usuario será notificado a través del th:errors etiqueta.

Ejecutemos nuestra aplicación y naveguemos a http: // localhost: 8080 / signup:

Anotación personalizada de @ValidPassword

Dependiendo de los requisitos del proyecto, a veces tenemos que definir un código personalizado específico para nuestras aplicaciones.

Como podemos hacer cumplir diferentes políticas y reglas, sigamos adelante y definamos una anotación personalizada que verifique una contraseña válida, que usaremos en nuestro UserDto clase.

Las anotaciones son solo metadatos del código y no contienen ninguna lógica empresarial. Solo pueden proporcionar información sobre el atributo (clase / método / paquete / campo) en el que está definido.

Creemos nuestro @ValidPassword anotación:

@Documented
@Constraint(validatedBy = PasswordConstraintValidator.class)
@Target({ FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ValidPassword {

    String message() default "Invalid Password";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Como puede ver, para crear una anotación usamos @interface palabra clave. Echemos un vistazo a algunas de las palabras clave y las comprendamos completamente antes de continuar:

  • @Documented: Un simple marcador de anotaciones que indica si agregar Anotación en Javadocs o no.
  • @Constraint: Marca una anotación como una Restricción de validación de bean. El elemento validatedBy especifica las clases que implementan la restricción. Crearemos el PasswordConstraintValidator clase un poco más tarde.
  • @Target: Es donde se pueden utilizar nuestras anotaciones. Si no especifica esto, la anotación se puede colocar en cualquier lugar. Actualmente, nuestra anotación se puede colocar sobre una variable de instancia y sobre otras anotaciones.
  • @Retention: Define durante cuánto tiempo se debe conservar la anotación. Hemos elegido RUNTIME para que pueda ser utilizado por el entorno de ejecución.

Para usar esto en nuestro UserDto clase simple anote el campo de contraseña:

@ValidPassword
private String password;

Validador de restricción de contraseña personalizado

Ahora que tenemos nuestra anotación, implementemos la lógica de validación para ella. Antes de eso, asegúrese de tener la dependencia Passay Maven incluida en su archivo pom.xml:

<dependency>
    <groupId>org.passay</groupId>
    <artifactId>passay</artifactId>
    <version>{$version}</version>
</dependency>

Puede verificar la última dependencia aquí.

Finalmente, escribamos nuestro PasswordConstraintValidator clase:

public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {

    @Override
    public void initialize(ValidPassword arg0) {
    }

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        PasswordValidator validator = new PasswordValidator(Arrays.asList(
            // at least 8 characters
            new LengthRule(8, 30),

            // at least one upper-case character
            new CharacterRule(EnglishCharacterData.UpperCase, 1),

            // at least one lower-case character
            new CharacterRule(EnglishCharacterData.LowerCase, 1),

            // at least one digit character
            new CharacterRule(EnglishCharacterData.Digit, 1),

            // at least one symbol (special character)
            new CharacterRule(EnglishCharacterData.Special, 1),

            // no whitespace
            new WhitespaceRule()

        ));
        RuleResult result = validator.validate(new PasswordData(password));
        if (result.isValid()) {
            return true;
        }
        List<String> messages = validator.getMessages(result);

        String messageTemplate = messages.stream()
            .collect(Collectors.joining(","));
        context.buildConstraintViolationWithTemplate(messageTemplate)
            .addConstraintViolation()
            .disableDefaultConstraintViolation();
        return false;
    }
}

Implementamos el ConstraintValidator interfaz que nos obliga a implementar un par de métodos.

Primero creamos un PasswordValidator objeto pasando una serie de restricciones que queremos aplicar en nuestra contraseña.

Las limitaciones se explican por sí mismas:

  • Debe tener entre 8 y 30 caracteres según lo definido por el LengthRule
  • Debe tener al menos 1 carácter en minúscula según lo definido por CharacterRule
  • Debe tener al menos 1 carácter en mayúscula según lo definido por CharacterRule
  • Debe tener al menos 1 carácter especial definido por el CharacterRule
  • Debe tener al menos 1 carácter de dígito según lo definido por el CharacterRule
  • No debe contener espacios en blanco definidos por WhitespaceRule

La lista completa de reglas que se pueden escribir con Passay se puede encontrar en el página web oficial.

Finalmente, validamos la contraseña y devolvimos true si pasa todas las condiciones. Si algunas condiciones fallan, agregamos todos los mensajes de error de la condición fallida en una cadena separada por “,” y luego la colocamos en el context y volvió false.

Ejecutemos nuestro proyecto nuevamente y escriba una contraseña no válida para verificar que la validación funcione:

Conclusión

En este artículo, cubrimos cómo hacer cumplir ciertas reglas de contraseña utilizando la biblioteca Passay. Creamos un validador de restricción de contraseña y anotaciones personalizado para esto y lo usamos en nuestra variable de instancia, y la lógica empresarial real se implementó en una clase separada.

Como siempre, el código de los ejemplos utilizados en este artículo se puede encontrar en Github.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad