Spring Security: registro de verificación de correo electrónico

S

Visión general

La primera acción que realiza un cliente después de visitar un sitio web es crear una cuenta, generalmente para realizar un pedido, reservar una cita, pagar un servicio, etc. Al crear una cuenta, es importante conservar la dirección de correo electrónico correcta en el sistema y verificar la propiedad del usuario.

Una estrategia común y eficaz para hacer esto es enviar un enlace de confirmación al registro de la publicación de correo electrónico del usuario. Una vez que el usuario hace clic en el enlace único, su cuenta se activa y puede realizar más acciones en el sitio web.

Spring nos permite implementar esta funcionalidad fácilmente, que es exactamente lo que haremos en este artículo.

Configuración del proyecto

Como siempre, es más fácil comenzar con un Bota de Spring proyecto usando Spring Initializr. Seleccione las dependencias para Web, Security, Mail, JPA, Thymeleaf y MySQL y genere el proyecto:

Estaremos usando Seguridad de Spring y Spring MVC para este proyecto. Para la capa de datos, usaremos Datos de Spring ya que ya nos proporciona operaciones CRUD para una entidad dada y derivación dinámica de consultas a partir de nombres de métodos de repositorio.

Además, usaremos Hibernar como el JPA proveedor y un MySQL base de datos.

Si está interesado en leer más sobre JPA, lo tenemos cubierto aquí: Una guía para Spring Data JPA.

Dependencias

Echemos un vistazo a las dependencias en el pom.xml archivo, que importa todas las bibliotecas necesarias según la descripción anterior:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
</dependencies>

Ahora, con el proyecto configurado, ¡podemos empezar a codificar!

Implementación

Propiedades de Spring

Comencemos configurando las propiedades de Spring en application.properties:

server.port = 8082
logging.level.org.springframework = WARN
logging.level.org.hibernate = WARN
logging.level.com.springsecurity.demo = DEBUG

####### Data-Source Properties #######
spring.datasource.url = jdbc:mysql://localhost:3306/demodb?useSSL=false
spring.datasource.username = username
spring.datasource.password = password
spring.datasource.driver-class-name = com.mysql.jdbc.Driver

###### JPA Properties ######
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.generate-ddl = true
spring.jpa.show-sql = true

###### Email Properties ######
spring.mail.host = smtp.gmail.com
spring.mail.port = 587
spring.mail.properties.mail.smtp.starttls.enable = true
spring.mail.username = [email protected]
spring.mail.password = examplepassword
spring.mail.properties.mail.smtp.starttls.required = true
spring.mail.properties.mail.smtp.auth = true
spring.mail.properties.mail.smtp.connectiontimeout = 5000
spring.mail.properties.mail.smtp.timeout = 5000
spring.mail.properties.mail.smtp.writetimeout = 5000

Estamos usando el servidor SMTP de Gmail para este ejemplo. Estoy ejecutando mi servidor tomcat en el puerto 8082.

Asegúrese de proporcionar las credenciales correctas de MySQL y de la cuenta de correo electrónico según su sistema. Con las propiedades de JPA configuradas, podemos comenzar con nuestra lógica comercial.

Configuración de JPA

Tenemos dos modelos para esta aplicación: User y ConfirmationToken:

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="user_id")
    private long userid;

    private String emailId;

    private String password;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    private boolean isEnabled;

    // getters and setters
}

Una clase POJO simple anotada con las anotaciones estándar de Spring.

Ahora pasemos al segundo modelo:

@Entity
public class ConfirmationToken {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="token_id")
    private long tokenid;

    @Column(name="confirmation_token")
    private String confirmationToken;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    @OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
    @JoinColumn(nullable = false, name = "user_id")
    private User user;

    public ConfirmationToken(User user) {
        this.user = user;
        createdDate = new Date();
        confirmationToken = UUID.randomUUID().toString();
    }

    // getters and setters
}

ConfirmationToken tiene una relación de uno a muchos con el User entidad. Desde que establecimos jpa.generate-ddl a true, Hibernate crea el esquema de tabla según las entidades anteriores.

Las claves primarias en ambas tablas están configuradas para auto-increment porque hemos anotado las columnas de ID en ambas clases con @Generated(strategy = GenerationType.AUTO).

Así es como se ve el esquema generado en la base de datos:

Ahora que la configuración de JPA está hecha, procederemos a escribir la capa de acceso a datos. Para eso usaremos Spring Data, ya que proporciona operaciones CRUD básicas listas para usar, que serán suficientes por el bien de este ejemplo.

Además, al usar Spring Data, libera el código de la placa de caldera, como obtener el administrador de entidades o obtener sesiones, etc.

@Repository("userRepository")
public interface UserRepository extends CrudRepository<User, String> {
    User findByEmailIdIgnoreCase(String emailId);
}

Spring Data proporciona automáticamente una implementación para consultar bases de datos sobre la base de un atributo, siempre que sigamos las especificaciones de Java Bean. Por ejemplo, en nuestro POJO, tenemos emailId como una propiedad de frijol y queremos encontrar el User por esa propiedad independientemente del caso.

Asimismo, implementamos el repositorio para el ConfirmationToken también:

public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> {
    ConfirmationToken findByConfirmationToken(String confirmationToken);
}

Un enfoque común que toman muchos desarrolladores cuando usan Spring Data es usar el repositorio CRUD básico provisto en otra clase de servicio y exponer los métodos de esa clase.

Esto mantiene el código de la aplicación acoplado libremente con las bibliotecas de Spring.

Servicio de correo electrónico

Una vez que el usuario completa el registro, debemos enviar un correo electrónico a la dirección de correo electrónico del usuario. Usaremos Spring Mail API para lograr esa funcionalidad.

Hemos agregado las propiedades de configuración para esto en el archivo de propiedades que se mostró anteriormente, por lo que podemos continuar con la definición de un servicio de correo electrónico:

@Service("emailSenderService")
public class EmailSenderService {

    private JavaMailSender javaMailSender;

    @Autowired
    public EmailSenderService(JavaMailSender javaMailSender) {
        this.javaMailSender = javaMailSender;
    }

    @Async
    public void sendEmail(SimpleMailMessage email) {
        javaMailSender.send(email);
    }
}

Hemos anotado la clase con @Service que es una variante del @Component anotación. Esto permite a Spring Boot descubrir el servicio y registrarlo para su uso.

Controlador y vista

Ahora tenemos todos los servicios listos para nuestro ejemplo y podemos continuar escribiendo el UserAccountController:

@Controller
public class UserAccountController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ConfirmationTokenRepository confirmationTokenRepository;

    @Autowired
    private EmailSenderService emailSenderService;

    @RequestMapping(value="/register", method = RequestMethod.GET)
    public ModelAndView displayRegistration(ModelAndView modelAndView, User user)
    {
        modelAndView.addObject("user", user);
        modelAndView.setViewName("register");
        return modelAndView;
    }

    @RequestMapping(value="/register", method = RequestMethod.POST)
    public ModelAndView registerUser(ModelAndView modelAndView, User user)
    {

        User existingUser = userRepository.findByEmailIdIgnoreCase(user.getEmailId());
        if(existingUser != null)
        {
            modelAndView.addObject("message","This email already exists!");
            modelAndView.setViewName("error");
        }
        else
        {
            userRepository.save(user);

            ConfirmationToken confirmationToken = new ConfirmationToken(user);

            confirmationTokenRepository.save(confirmationToken);

            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setTo(user.getEmailId());
            mailMessage.setSubject("Complete Registration!");
            mailMessage.setFrom("[email protected]");
            mailMessage.setText("To confirm your account, please click here : "
            +"http://localhost:8082/confirm-account?token="+confirmationToken.getConfirmationToken());

            emailSenderService.sendEmail(mailMessage);

            modelAndView.addObject("emailId", user.getEmailId());

            modelAndView.setViewName("successfulRegisteration");
        }

        return modelAndView;
    }

    @RequestMapping(value="/confirm-account", method= {RequestMethod.GET, RequestMethod.POST})
    public ModelAndView confirmUserAccount(ModelAndView modelAndView, @RequestParam("token")String confirmationToken)
    {
        ConfirmationToken token = confirmationTokenRepository.findByConfirmationToken(confirmationToken);

        if(token != null)
        {
            User user = userRepository.findByEmailIdIgnoreCase(token.getUser().getEmailId());
            user.setEnabled(true);
            userRepository.save(user);
            modelAndView.setViewName("accountVerified");
        }
        else
        {
            modelAndView.addObject("message","The link is invalid or broken!");
            modelAndView.setViewName("error");
        }

        return modelAndView;
    }
    // getters and setters
}

Echemos un vistazo a los métodos en el controlador, qué hacen y qué vistas devuelven.

displayRegistration() – Punto de partida para el usuario en nuestra aplicación. Tan pronto como el usuario abre nuestra aplicación, se le muestra la página de registro a través de este método.

También hemos agregado el user Objeto a la vista. los <form> La etiqueta en la página de registro también incluye este objeto y usaremos los campos en el formulario para completar los campos del objeto.

Este objeto, con la información completa, se conservará en la base de datos.

Así es como se ve en la página:

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Register</title>
    </head>
    <body>
        <form action="#" th:action="@{/register}" th:object="${user}" method="post">
            <table>
                <tr>
                    <td><label for="firstName">First Name</label></td>
                    <td><input th:field="*{firstName}" type="text" name="firstName"></input></td>
                </tr>
                <tr>
                    <td><label for="lastName">Last Name</label></td>
                    <td><input th:field="*{lastName}" type="text" name="lastName"></input></td>
                </tr>
                <tr>
                    <td><label for="emailId">Email</label></td>
                    <td><input th:field="*{emailId}" type="text" name="emailId"></input></td>
                </tr>
                <tr>
                    <td><label for="password">Password</label></td>
                    <td><input th:field="*{password}" type="password" name="password"></input></td>
                </tr>
                <tr>
                    <td><input type="reset" value="Clear"/></td>
                    <td><input type="submit" value="Submit"></input></td>
                </tr>
            </table>
        </form>
    </body>
</html>

registerUser() – Acepta los datos de usuario introducidos en la página de registro. Spring MVC automáticamente hace que la entrada del usuario esté disponible para nosotros en el método.

Guardamos los detalles del usuario en la tabla de usuarios y creamos un token de confirmación aleatorio. El token se guarda con el usuario emailId en el confirmation_token tabla y se envía a través de una URL al correo electrónico del usuario para su verificación.

Se muestra al usuario una página de registro exitosa:

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Registration Success</title>
    </head>
    <body>
        <center>
            <span th:text="'A verification email has been sent to: ' + ${emailId}"></span>
        </center>
    </body>
</html>

Por último, una vez que se accede a la URL del correo electrónico, confirmUserAccount() se llama al método.

Este método valida el token que no debe estar vacío y debe existir en la base de datos; de lo contrario, se muestra al usuario una página de error (error.html):

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Registration Success</title>
    </head>
    <body>
        <center>
            <span th:text="${message}"></span>
        </center>
    </body>
</html>

Si no hay problemas de validación, se verifica la cuenta asociada con el token. Se muestra al usuario un mensaje de activación exitosa:

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Congratulations!</title>
    </head>
    <body>
        <center>
            <h2>Congratulations! Your account has been activated and email is verified!</h2>
        </center>
    </body>
</html>

Configuración de seguridad de Spring

Configuremos ahora el módulo Spring Security para asegurar nuestra aplicación web. Necesitamos asegurarnos de que no se requiera autenticación para /register y /confirm URL, ya que son las páginas de destino de un nuevo usuario:

@Configuration
@EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .antMatchers("/register").permitAll()
                .antMatchers("/confirm").permitAll();
        }
}

Por último, necesitaremos un main método como punto de partida para nuestra aplicación Spring Boot:

@SpringBootApplication
public class RunApplication {
    public static void main(String[] args) {
        SpringApplication.run(RunApplication.class, args);
    }
}

los @SpringBootApplication La anotación indica a Spring Boot que cargue toda la configuración y las clases de componentes y también habilita la configuración automática. Esta es una de las grandes características de Spring Boot, podemos ejecutarlo usando un método principal simple.

Ejecutando la Aplicación

Comenzamos la prueba seleccionando y ejecutando el RunApplication.java. Esto inicia el servidor Tomcat integrado en el puerto 8082 y nuestra aplicación se implementa.

A continuación, abramos el navegador y accedamos a nuestra aplicación:

Al ingresar la información requerida, el usuario enviará:

Tras el envío, se envía un correo electrónico al usuario para verificar el correo electrónico:

Siguiendo el enlace, la cuenta se verifica mediante un token único y el usuario es redirigido a la página de éxito:

Así es como se ve en la base de datos:

Conclusión

La verificación de correo electrónico es un requisito muy común para las aplicaciones web. Casi cualquier tipo de registro requiere una cuenta de correo electrónico verificada, especialmente si el usuario ingresa algún tipo de información sensible en el sistema.

En este artículo, escribimos una aplicación Spring Boot simple para generar tokens únicos para nuevos usuarios, enviarles un correo electrónico y verificarlos en nuestro sistema.

Si está interesado en jugar con el código fuente, como siempre, está disponible 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 para su correcto funcionamiento. 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