Rails 3.0, HTTP y HTTPS conviviendo en armonía

Uno de los últimos problemas con los que me he tenido que enfrentar ha sido la labor de conseguir que convivan rutas HTTP y HTTPS en una aplicación Rails 3.0 de forma que sólo se utilice HTTPS para unas pocas acciones determinadas y el resto de la aplicación se pueda navegar sin encriptar.

Hay muchos artículos en la red explicando cómo conseguir parte de la tarea, pero en todos los casos la información me resultaba incompleta porque:

  • En muchos de los ejemplos encontrados había que convertir la aplicación completa a HTTPS (yo sólo quería aplicar la encriptación a unas pocas acciones)
  • En la mayoría de casos había que instalar un certificado para trabajar en local (yo en local quería seguir trabajando sin SSL, utilizándolo sólo en producción)
  • ¡Algunas soluciones incluso pasaban por la modificación de Webrick para poder trabajar en la máquina de desarrollo con los 2 protocolos a la vez!
  • En la mayoría de soluciones, una vez que se forzaba el uso de SSL para una ruta, el resto de rutas no volvían a ser HTTP.
  • La solución utilizando before_filters en todos los controladores para desactivar HTTPS me resultaba muy engorrosa debido a que la aplicación en la que trabajo tiene muchos controladores, además de no ceñirse nada a los principios DRY que siempre procuro tener presentes.

Después de 2 días de investigación la solución que fabriqué fue:

1. Activar la posibilidad de trabajar con los protocolos HTTP y HTTPS en paralelo a través de la gema «Rack::SSL», añadiendo en config/environments/production.rb:

config.middleware.insert_before ActionDispatch::Static, Rack::SSL, :exclude => proc { |env| env['HTTPS'] != 'on' }

según la documentación encontrada aquí
Por supuesto para que esto funcione, debemos incluir Rack::SSL en nuestro Gemfile:

gem 'rack-ssl', :require => 'rack/ssl'

y actualizar nuestro paquete usando el bundler:
bundle install

2. Añadir un filtro previo a la ejecución de cualquier método de cualquier controlador que distinga si la acción llamada es una acción segura o no. Es decir, añadir en mi application_controller.rb:

before_filter :decidir_protocolo

y la definición de este método:

private

def decidir_protocolo
  acciones_seguras = {
    :controlador1 => ["accion1", "accion2"],
    :controlador2 => ["accion3"],
    :controlador3 => ["accion4","accion5","accion6"]
  }
  #sólo se aplica el filtro si estamos en producción
  if Rails.env.production?
    #si se trata de una acción de las que he definido como seguras
    if accion = acciones_seguras[params[:controller].to_sym] and accion.include? params[:action]
      #y si el protocolo de llamada no es ssl
      if !request.ssl?
        #fuerzo el protocolo https
        redirect_to :protocol => 'https'
      end
    #en caso de no tratarse de una acción segura
    else
      #y de venir de una acción que sí lo era
      if request.ssl?
        #fuerzo el protocolo http
        redirect_to :protocol => 'http'
      end
    end
  end
end

Me parece una solución muy buena que no tiene ninguno de los inconvenientes de las soluciones encontradas, que me echaban para atrás.

¡Espero que te ayude!, aunque Rails 3.1 ya viene con la capacidad de gestionar SSL de serie… pero no sé si lo hará tan bien 😉

Share this post

Related post

  1. ecastelo 2012.02.03 4:09pm

    Gracias por tu aportacion, pero tengo una duda y me gustaria que me dieses una sugerencia si puede ser posible. En mi caso especifico, despues de lanzar la accion que debe ser ejeutada bajo HTTPS necesito volver al protocolo inicial. Para ello en una vble de session guardo esa informacion pero no se cual seria la forma mas correcta para lanzar ese metodo. Podrias darme una sugerencia. Muchas gracias. Un saludo

  2. victor hazbun 2012.08.17 6:24pm

    Hola Juan,

    Estas seguro que es:

    and accion.include? params[:action]

    la variable action va a ser nil en ese caso y el ejemplo va a explotar. Por favor corrigelo.

  3. Juanfer 2012.09.05 5:23pm

    Hola Víctor,
    estuve revisando el posible fallo que reportabas, pero no lo encontré. Quizá lo dices porque la asignación de la variable accion está dentro del mismo condicional, cosa que en cualquier otro lenguaje (que conozca) fallaría, sin embargo en ruby puedes hacer cosas como esas sin preocuparte, siempre y cuando el orden sea lógico: ruby ejecuta la asignación, luego la operación booleana y finalmente el código del if con esa única línea de código, por eso ruby es tan chulo 🙂 . Un par de ejemplos lo ilustran mejor:
    if (a=5 and a<10) 1 else 2 end => resultado: 1
    if (a=5 and a>10) 1 else 2 end => resultado: 2
    Si no es ese el problema, por favor explícamelo mejor para volver a revisarlo. En cualquier caso gracias por comentar!

{ Piensa / Think }

"This is a waste of life. [...] the entire educational system in the modern day is nothing more than a cookie cutter processing plant that prepares humans for mostly predefined occupational roles. This element of human life has become so traditionally ingrained, that many falsely consider the nature of ‘having a job’ some form of human instinct. Even parents will ask their kids “What do you want to be when you grow up?” as though there was only one thing. This is disturbing and a violation of human potential." - The Zeitgeist Movement


"He aprendido que hay cosas que pueden ser comprendidas pero que nunca podrán ser explicadas con palabras sin desvirtuar su grandeza" - Andrés Pascual


"You never change things by fighting the existing reality. To change something, build a new model that makes the existing model obsolete." - Buckminster Fuller


"... I am the master of my fate. I am the captain of my soul." - William Ernest Henley