Validaci贸n de contrase帽a personalizada de Spring

    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.

    Etiquetas:

    Deja una respuesta

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