ASP.NET MVC: Encriptar RouteValues

Muy buenas! El otro día publicaba en mi blog una solución para encriptar la querystring en ASP.NET MVC. A raiz de este post, me preguntaron si era posible hacer lo mismo pero en el caso de que tengamos URL amigables y como se podría hacer.

La respuesta es que sí, que se puede hacer y que a diferencia del caso de la querystring tenemos dos opciones.

Opción 1 – Value Provider

En este caso el planteamiento es el mismo que en el post anterior, debemos crear un value provider que recoja los datos y los desencripte.

Así, empezamos por crear la factoría de nuestro nuevo value provider:

public class RouteValuesEncriptedValueProviderFactory : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        return new RouteValuesEncriptedValueProvider(controllerContext);
    }
}

Y el código de nuestro value provider, muy parecido al del post anterior. La diferencia está en que en lugar de mirar en la request los valores de la querystring, miramos en RouteData que es donde están guardados todos los valores de ruta. Los valores de ruta se extraen a partir del PathInfo, es decir la parte de la URL que va después del host hasta el inicio de la querystring. El código es muy sencillo:

class RouteValuesEncriptedValueProvider : DictionaryValueProvider<string>
{

    public RouteValuesEncriptedValueProvider(System.Web.Mvc.ControllerContext controllerContext)
        : base(GetRouteValueDictionary(controllerContext), Thread.CurrentThread.CurrentCulture)
    {
    }

    private static IDictionary<string, string> GetRouteValueDictionary(ControllerContext controllerContext)
    {
        var dict = new Dictionary<string, string>();
        foreach (var key in controllerContext.RouteData.Values.Keys.Where(x => x.First() == '_'))
        {
            var value = controllerContext.RouteData.GetRequiredString(key);
            var decripted = Crypto.DecryptValue(value);
            dict.Add(key.Substring(1), decripted);
        }
        return dict;
    }
}

El funcionamiento es análogo al del value provider del post anterior y actúa sobre los route values cuyo nombre empieze por un subrayado (aunque por supuesto esto va a gustos del consumidor). Dado que el nombre de los route values se establece en la tabla de rutas, debemos cambiar la definición para que allí los nombres empiecen por un subrayado:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{_id}",
    defaults: new { controller = "Home", action = "Index", _id = UrlParameter.Optional }
);

Al igual que en el value provider del post anterior, al añadir el valor desencriptado de un route value, lo hace eliminando el subrayado inicial, por lo que en nuestros controladores podemos declarar el parámetro como “id” y recibiremos el valor desencriptado (si lo declarásemos como _id lo recibiríamos encriptado).

Pero, en este caso especial tenemos otra opción distinta para desencriptar los valores de ruta…

Opción 2: Un custom route handler

Hemos visto como este value provider nuevo no accede a la request, ni mira la URL. En su lugar accede a los valores de ruta (controllerContext.RouteData). Pero… ¿quien rellena esto? Pues el route handler.

Cada vez que una ruta es seleccionada, se ejecuta su route handler, que es el encargado de rellenar los valores de ruta. ASP.NET MVC viene con un route handler por defecto, pero nos podemos crear el nuestro. En este caso, nada nos impide crear un route handler que desencripte los valores antes de añadirlos:

public class UnencriptedRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
    {
        foreach (var rd in requestContext.RouteData.Values.Where(x=>x.Key.First()== '_').ToList())
        {
            var value = rd.Value.ToString();
            var decrypted = !string.IsNullOrEmpty(value) ?
                Crypto.DecryptValue(value) :
                string.Empty;
            requestContext.RouteData.Values.Remove(rd.Key);
            requestContext.RouteData.Values.Add(rd.Key.Substring(1), decrypted);
        }


        return base.GetHttpHandler(requestContext);
    }
}

Si miramos el código, vemos como accedemos a los route values (que en este punto ya están relleanados por el route handler por defecto del cual derivamos) y buscamos aquellos que empiecen por un subrayado y en su lugar los colocamos de nuevo pero desencriptados (y sin subrayar).

Ahora debemos indicarle a ASP.NET MVC que use este route handler nuestro, y eso se hace en la definición de la tabla de rutas:

routes.Add(
    "Default",
    new Route("{controller}/{action}/{_id}",
                new RouteValueDictionary(new { controller = "Home", action = "Index", _id = UrlParameter.Optional }),
                new UnencriptedRouteHandler())
);

En lugar de usar MapRoute, usamos el método Add, el cual espera un objeto Route al cual de lo podemos especificar cual será su route handler. Además fijaos en que hemos declarado _id en lugar de id, eso es porque nuestro route handler actuará tan solo sobre los route values que empiecen por el subrayado.

Con esto ya hemos terminado. Ahora, si generamos URLs que tengan el _id encriptado:

@Html.ActionLink("Test", "Test", 
new {_id=MvcApplication4.Crypto.EncryptValue("bar value")}, null)

Podemos declarar en el controlador la acción que espere un parámetro llamado id que tendrá el valor ya desencriptado. A diferencia de la opción anterior, ahora si usamos _id como parámetro recibiremos null. El valor encriptado se ha perdido y no existe a todos los efectos.

Usando esta segunda opción no hay necesidad alguna de crear un value provider propio.

¿Y las diferencias?

Las diferencias entre las dos opciones son básicamente el momento en que se produce la desencriptación:

  1. En la primera opción (value provider), la desencriptación se produce tan solo en el momento de enlazar los parámetros al controlador. Y la desencriptación es tan solo visible para el model binder. Así p.ej. si hay filtros que actuen sobre los valores de ruta, estos verán los valores encriptados.
  2. En la segunda opción, justo al inicio de procesarse la request, antes incluso de decidirse que controlador y que acción deben ser invocados, los valores de ruta son desencriptados. El valor encriptado desaparece y todo el mundo (y no sólo el model binder) tiene acceso a los valores desencriptados.

Si me preguntáis en mi caso que opción escogería… probablemente la segunda 🙂

Un saludo!

PD: En el caso de la querystring no existe la posibilidad de esta segunda opción. Los route handlers actúan tan solo sobre los valores de ruta, no sobre la querystring, por lo que el value provider es la única opción.

Deja un comentario

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