Seguridad en Servicios Web SOAP ASP.NET - Token de acceso (II)

En el anterior artículo de este Blog Seguridad en Servicios Web SOAP ASP.NET - Token de acceso (I), vimos cómo implementar un sistema de autenticación de usuarios basado en Tokens, para Web Services SOAP de ASP.NET.

En esta ocasión, y para complementar el artículo anterior, veremos cómo consumir un Web Service SOAP con seguridad basada en Tokens de acceso desde una aplicación ASP.NET Web Forms, teniendo en cuenta aspectos como la serialización, encriptación y almacenamiento del Token en el cliente.

webservice-token6

 

Creando nuestra aplicación Cliente ASP.NET con Visual Studio

Para este ejemplo, utilizaremos Visual Studio 2019 con todas las actualizaciones necesarias para usar el SDK de .NET Framework 4.6.x.

En primer lugar, crearemos un nuevo Proyecto del tipo Aplicación web ASP.NET (.NET Framework) del tipo Web Forms, con el nombre MiApp.WebClient.

A continuación, crearemos el Cliente o Proxy que consumirá el Servicio Web (ClientesWS.asmx) creado anteriormente en el artículo Seguridad en Servicios Web SOAP ASP.NET - Token de acceso (I).

Esto lo haremos creando una nueva Referencia de servicio (Botón derecho sobre el Proyecto: Agregar > Referencia de servicio... ) al Web Service (por ejemplo: https://localhost:44394/ClientesWS.asmx) con el Nombre ClientesWS.

webservice-token4

webservice-token5

Configuraciones previas - Web.config

Como ya sabemos, el Servicio Web ClientesWS.asmx sobre el cual trabajaremos, requiere de un Usuario y Contraseña para poder devolvernos el Token de acceso que nos permitirá acceder a sus recursos disponibles.

Es por esto que definiremos en el Web.config de la aplicación este Usuario y Contraseña, para poder utilizarlos mas adelante.

Dentro de la etiqueta <system.web> añadiremos el siguiente código:

  <appSettings>
    <add key="UsuarioWS" value="admin" />
    <add key="PasswordWS" value="admin" />
  </appSettings>

Por otra parte, también vamos a necesitar encriptar el Token de acceso que el Web Service nos devuelve. Esto lo haremos principalmente por cuestiones de seguridad, ya que almacenar en el Cliente esta información de manera visible, no es una buena práctica.

Para ello definiremos en el Web.config un <machineKey /> personalizado dentro de la sección <system.web>. Su uso lo veremos y explicaremos más adelante:

<!-- MACHINEKEY PARA ENCRIPTAR -->
    <machineKey validationKey="57E8CA69ADAB0E32F79A74B01DA32B2E05DC6D166ED7AAF40F84991E5E9BF0BB8C9EE4D3BEE998F845FCEE64951160C5572ACB3F75A004A1EBB2779F286E52B8" 
                decryptionKey="77E8F78B0BB51A741031088D1EE9DC0195E3447D862BB73B18D32E086784DDE4" validation="SHA1" decryption="AES" />

 

Creando el diseño del formulario - Default.aspx

A continuación, crearemos el diseño de la página (Default.aspx) que se encargará de interactuar con el Web Service para obtener el Token de acceso y mostrarnos los datos.

Básicamente consistirá en una tabla Html donde mostraremos los datos devueltos por el Web Service, y un par de botones para obtener el Token y actualizar los datos.

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="MiApp.WebClient._Default" %>

<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">

    <div class="container">
            <h4>
                <label>Token: </label> 
                <span runat="server" id="idTokenLabel" class="label label-success"></span>
            </h4>

            <%--Control de servidor ListView--%>
            <asp:ListView runat="server" ID="idListView" 
                          ItemPlaceholderID="itemPlaceHolder" 
                          OnItemDataBound="idListView_ItemDataBound">

                <%--Plantilla de diseño de la tabla Html--%>
                <LayoutTemplate>
                    <table id="ListViewTable" class="table table-bordered">
                        <%--Cabecera de la tabla Html--%>
                        <thead>
                            <tr>
                                <th scope="col">Nombre Cliente</th>
                                <th scope="col">Nombre Empresa</th>
                                <th scope="col">Email</th>
                            </tr>
                        </thead>
                        <%--Cuerpo de la tabla Html--%>
                        <tbody>
                            <%--Control de servidor PlaceHolder, elemento que
                            contendrá la plantilla <ItemTemplate>--%>
                            <asp:PlaceHolder runat="server" id="itemPlaceHolder">
                            </asp:PlaceHolder>
                        </tbody>
                    </table>
                </LayoutTemplate>

                <%--Plantilla de los elementos dinámicos de la tabla Html filas y columnas--%>
                <ItemTemplate>
                    <tr>                                
                        <td runat="server" id="NombreCliente"></td>
                        <td runat="server" id="NombreEmpresa"></td>
                        <td runat="server" id="Email"></td>
                    </tr>
                </ItemTemplate>            

            </asp:ListView>

            <h4><span runat="server" id="idExcepcionLabel" class="label label-danger"></span></h4>
    </div>

    <div class="container">
        <div class="row">            
            <asp:Button runat="server" ID="idActualizar" OnClick="idActualizar_Click" Text="Obtener datos" CssClass="btn btn-primary" />            
            <asp:Button runat="server" ID="idSolicitarToken" OnClick="idSolicitarToken_Click" Text="Solicitar Token" CssClass="btn btn-danger" />
        </div>
    </div>

</asp:Content>

 

El CodeBehind - Default.aspx.cs

En este punto, ya estaremos en disposición de crear nuestro Cliente Web que consumirá el Web Service. Básicamente, todo el desarrollo lo centraremos sobre la clase ClientesWS.ClientesWSSoapClient, la cual nos dará acceso a los métodos del Servicio Web GetTokenAcceso()GetClientes().

El código C# para nuestra página Default.aspx sería el siguiente:

    public partial class _Default : Page
    {
        private ClientesWS.ClientesWSSoapClient _clientesWSClient;
        private ClientesWS.TokenAcceso _tokenAccesoWS;

        // RECUPERAMOS DEL web.config EL LOS DATOS DE ACCESO AL WEB SERVICE.
        private string _usuarioWS = ConfigurationManager.AppSettings["UsuarioWS"];
        private string _passwordWS = ConfigurationManager.AppSettings["PasswordWS"];        

        List<ClientesWS.Cliente> _clientes = new List<ClientesWS.Cliente>();

        protected void Page_Load(object sender, EventArgs e)
        {
            idExcepcionLabel.Visible = false;
            idActualizar.Visible = true;
            idSolicitarToken.Visible = true;
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRenderComplete(e);
            idListView.DataSource = _clientes;
            idListView.DataBind();
        }

        protected void idListView_ItemDataBound(object sender, ListViewItemEventArgs e)
        {
            if (e.Item.ItemType == ListViewItemType.DataItem)
            {
                ClientesWS.Cliente cliente = (ClientesWS.Cliente)e.Item.DataItem;
                ((HtmlTableCell)e.Item.FindControl("NombreCliente")).InnerText = cliente.NombreCliente;
                ((HtmlTableCell)e.Item.FindControl("NombreEmpresa")).InnerText = cliente.NombreEmpresa;
                ((HtmlTableCell)e.Item.FindControl("Email")).InnerText = cliente.Email;
            }
        }

        protected void idSolicitarToken_Click(object sender, EventArgs e)
        {
            // CREAMOS LA CABECERA SOAP CON LOS DATOS DE ACCESO
            // PARA EL WEBMETHOD GetTokenAcceso()
            ClientesWS.CabeceraSoapAcceso cabeceraSoapAcceso = new ClientesWS.CabeceraSoapAcceso();
            cabeceraSoapAcceso.Usuario = _usuarioWS;
            cabeceraSoapAcceso.Password = _passwordWS;

            // SOLICITAMOS EL TOKEN DE ACCESO AL WEB SERVICE Y
            // LO ALMACENAMOS EN UNA COOKIE
            try
            {
                // SOLICITAMOS EL TOKEN DE ACCESO AL WEB SERVICE
                _clientesWSClient = new ClientesWS.ClientesWSSoapClient();
                _tokenAccesoWS = _clientesWSClient.GetTokenAcceso(cabeceraSoapAcceso);
                idTokenLabel.InnerText = _tokenAccesoWS.Token.ToString() + " | " +
                                         _tokenAccesoWS.Nombre + "" + _tokenAccesoWS.Apellidos + " | " +
                                         _tokenAccesoWS.Email + " | " + _tokenAccesoWS.Rol;

                // SERIALIZAMOS A JSON EL OBJETO DE TIPO ClientesWS.TokenAcceso 
                // PARA PODER ALMAZCENARLO EN UNA COOKIE.
                var jsonTokenAccesoWS = JsonConvert.SerializeObject(_tokenAccesoWS);

                // ENCRIPTAMOS EL OBJETO ClientesWS.TokenAcceso
                var encTokenAccesoWS = Encriptar(jsonTokenAccesoWS);

                // ALMACENAMOS EN UNA COOKIE EL OBJETO ClientesWS.TokenAcceso ENCRIPTADO
                Response.SetCookie(new HttpCookie("TokenCookie", encTokenAccesoWS));

                idActualizar.Visible = true;
                idSolicitarToken.Visible = false;
            }
            catch (Exception ex)
            {
                idExcepcionLabel.Visible = true;
                idExcepcionLabel.InnerText = ex.Message;
            }
        }


        protected void idActualizar_Click(object sender, EventArgs e)
        {
            // COMPRUEBA SI EXISTE UNA COOKIE CON EL TOKEN DE ACCESO            
            if (Request.Cookies["TokenCookie"] != null)
            {
                // SI EXISTE, ACCEDEMOS AL WEB SERVICE

                // OBTENEMOS EL VALOR DE LA COOKIE
                var cookieValue = Request.Cookies["TokenCookie"].Value;

                // DESENCRIPTAMOS EL VALOR DE LA COOKIE
                var desTokenAccesoWS = Desencriptar(cookieValue);

                // DESERIALIZAMOS EL VALOR DE LA COOKIE
                // A UN OBJETO DEL TIPO ClientesWS.TokenAcceso
                _tokenAccesoWS = JsonConvert.DeserializeObject<ClientesWS.TokenAcceso>(desTokenAccesoWS);
                idTokenLabel.InnerText = _tokenAccesoWS.Token.ToString() + " | " +
                                         _tokenAccesoWS.Nombre + "" + _tokenAccesoWS.Apellidos + " | " +
                                         _tokenAccesoWS.Email + " | " + _tokenAccesoWS.Rol;

                // CREAMOS LA CABECERA SOAP CON EL TOKEN DE ACCESO
                // PARA EL WEBMETHOD GetClientes() 
                ClientesWS.CabeceraSoapToken cabeceraSoapToken = new ClientesWS.CabeceraSoapToken();
                cabeceraSoapToken.TokenAcceso = _tokenAccesoWS.Token.ToString();

                // ACCEDEMOS A LOS DATOS DEL WEB SERVICE CON EL TOKEN DE ACCESO.
                try
                {
                    _clientesWSClient = new ClientesWS.ClientesWSSoapClient();
                    _clientes = _clientesWSClient.GetClientes(cabeceraSoapToken).ToList();
                    idActualizar.Visible = true;
                    idSolicitarToken.Visible = false;
                }
                catch (Exception ex)
                {
                    idExcepcionLabel.Visible = true;
                    idExcepcionLabel.InnerText = ex.Message;
                    idActualizar.Visible = false;
                    idSolicitarToken.Visible = true;
                }                
            }
            else
            {
                // SI NO EXISTE LA COOKIE ...
                idTokenLabel.InnerText = string.Empty;
                idActualizar.Visible = false;
                idSolicitarToken.Visible = true;
            }
        }

        private string Encriptar (string textoCookie)
        {
            if (!string.IsNullOrEmpty(textoCookie))
            {
                // Descomponemos texto de la cookie en bytes.
                byte[] _textoCookie = Encoding.UTF8.GetBytes(textoCookie);

                // Encriptamos el texto de la Cookie.
                byte[] _TextoEncriptado = MachineKey.Protect(_textoCookie);

                // Devolvemos el texto encriptado de la cookie.
                return HttpServerUtility.UrlTokenEncode(_TextoEncriptado);
            }
            else
            {
                return string.Empty;
            }            
        }

        private string Desencriptar(string textoCookie)
        {
            if (!string.IsNullOrEmpty(textoCookie))
            {
                // Descomponemos valor de la cookie en bytes.
                byte[] _textoCookie = HttpServerUtility.UrlTokenDecode(textoCookie);

                // Desencriptamos el texto de la Cookie
                byte[] _TextoDesencriptado = MachineKey.Unprotect(_textoCookie);

                // Devolvemos el texto desencriptado de la cookie.
                return Encoding.UTF8.GetString(_TextoDesencriptado);
            }
            else
            {
                return string.Empty;
            }
        }

    }

 

El almacenamiento en Cliente del Token de acceso

Como vemos en el código, Cuando solicitamos el Token de acceso al Servicio Web mediante el método GetTokenAcceso(cabeceraSoapAcceso), este nos devuelve un objeto del tipo ClientesWS.TokenAcceso el cual contiene además del propio Token, otra información adicional acerca del usuario que se validó en el sistema del información del Web Service.

    public class TokenAcceso
    {
        public Guid Id { get; set; }
        public string Nombre { get; set; }
        public string Apellidos { get; set; }
        public string Email { get; set; }
        public string Rol { get; set; }
        // EL TOKEN DE ACCESO.
        public Guid Token { get; set; }
    }

Para conseguir la persistencia de este objeto (ClientesWS.TokenAcceso) en el Cliente, lo que hacemos es almacenarlo en una Cookie del explorador, para poder utilizarlo posteriormente en las siguientes llamadas al Web Service.

Serializar y encriptar la información

Por cuestiones de seguridad, es fundamental que la información del Token de acceso que almacenamos en la Cooke, esté encriptada. Para ello lo que hacemos es serializar el objeto ClientesWS.TokenAccesoa JSON:

// SERIALIZAMOS A JSON EL OBJETO DE TIPO ClientesWS.TokenAcceso 
// PARA PODER ALMAZCENARLO EN UNA COOKIE.
var jsonTokenAccesoWS = JsonConvert.SerializeObject(_tokenAccesoWS);

y posteriormente lo encriptamos antes de almacenarlo en la Cookie. Para esto, hemos definido los métodos privados  Encriptar(string textoCookie)Desencriptar(string textoCookie) que vimos anteriormente en el código:

        private string Encriptar (string textoCookie)
        {
            if (!string.IsNullOrEmpty(textoCookie))
            {
                // Descomponemos texto de la cookie en bytes.
                byte[] _textoCookie = Encoding.UTF8.GetBytes(textoCookie);

                // Encriptamos el texto de la Cookie.
                byte[] _TextoEncriptado = MachineKey.Protect(_textoCookie);

                // Devolvemos el texto encriptado de la cookie.
                return HttpServerUtility.UrlTokenEncode(_TextoEncriptado);
            }
            else
            {
                return string.Empty;
            }            
        }

        private string Desencriptar(string textoCookie)
        {
            if (!string.IsNullOrEmpty(textoCookie))
            {
                // Descomponemos valor de la cookie en bytes.
                byte[] _textoCookie = HttpServerUtility.UrlTokenDecode(textoCookie);

                // Desencriptamos el texto de la Cookie
                byte[] _TextoDesencriptado = MachineKey.Unprotect(_textoCookie);

                // Devolvemos el texto desencriptado de la cookie.
                return Encoding.UTF8.GetString(_TextoDesencriptado);
            }
            else
            {
                return string.Empty;
            }
        }

Importante: Es en este punto, donde vemos la utilidad del <machineKey />que definimos anteriormente en el Web.Config de la aplicación. Este <machineKey /> se utilizará como clave única para encriptar y desencriptar la Cookie  con los métodos MachineKey.Protect(_textoCookie)MachineKey.Unprotect(_textoCookie). Para más información, pueden consultar el artículo Encriptar y desencriptar cookies en una aplicación ASP.NET.

 

Consideraciones finales 

Como hemos visto en estos dos artículos, aplicar seguridad basada en Tokens de acceso a servicios Web SOAP de ASP.NET, es un proceso relativamente sencillo de implementar y de resultados bastante óptimos.

Por su puesto, este tutorial solo trata de dar una idea orientativa sobre la seguridad en los servicios Web SOAP. Cualquier sugerencia o mejora en la estructura, diseño e implementación, será bienvenida y contestada en la sección de comentarios de esta artículo.

webservice-token7

 

   EtiquetasSOAP ASP.NET Web Forms Web Services

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

Enviando ...

  Comentarios

No hay comentarios para este Post.


Perfil para Rafael Acosta en Stack Overflow en español, preguntas y respuestas para programadores y profesionales de la informática.


  Etiquetas

.NET Core .NET Framework .NET MVC .NET Standard AJAX ASP.NET ASP.NET Core ASP.NET MVC Bootstrap Buenas prácticas C# Cookies Entity Framework JavaScript jQuery JSON JWT PDF Pruebas Unitarias Seguridad SEO SOAP Sql Server SqLite Swagger Validación Web API Web Forms Web Services WYSIWYG

  Nuevos


  Populares















Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto