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 😉
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
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.
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!