ruby on rails - tutorial - Diseñar límite de una sesión por usuario a la vez
tutorial django (7)
Mi aplicación usa Rails 3.0.4 y Devise 1.1.7.
Estoy buscando una forma de evitar que los usuarios compartan cuentas, ya que la aplicación es un servicio basado en suscripción. He estado buscando durante más de una semana, y todavía no sé cómo implementar una solución. Espero que alguien haya implementado una solución y pueda orientarme en la dirección correcta.
Solución (¡Gracias a todos por sus respuestas y comprensión!)
En la aplicación controller.rb
before_filter :check_concurrent_session
def check_concurrent_session
if is_already_logged_in?
sign_out_and_redirect(current_user)
end
end
def is_already_logged_in?
current_user && !(session[:token] == current_user.login_token)
end
En session_controller que anula el controlador Devise Sessions:
skip_before_filter :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save
end
En migración AddLoginTokenToUsers
def self.up
change_table "users" do |t|
t.string "login_token"
end
end
def self.down
change_table "users" do |t|
t.remove "login_token"
end
end
Así es como resolví el problema de la sesión duplicada.
routes.rb
devise_for :users, :controllers => { :sessions => "my_sessions" }
controlador my_sessions
class MySessionsController < Devise::SessionsController
skip_before_filter :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save(validate: false)
end
end
application_controller
def check_concurrent_session
if duplicate_session?
sign_out_and_redirect(current_user)
flash[:notice] = "Duplicate Login Detected"
end
end
def duplicate_session?
user_signed_in? && (current_user.login_token != session[:token])
end
Modelo de usuario Agregue un campo de cadena a través de una migración llamada login_token
Esto anula el controlador predeterminado Devise Session, pero hereda de él también. En una nueva sesión, se crea un token de sesión de inicio de sesión y se almacena en login_token
en el modelo de Usuario. En el controlador de la aplicación llamamos a check_concurrent_session
que check_concurrent_session
la check_concurrent_session
y redirige al check_concurrent_session
después de llamar a la duplicate_session?
función.
No es la forma más limpia de hacerlo, pero definitivamente funciona.
Descubrí que la solución en la publicación original no funcionaba para mí. Quería que el primer usuario se desconectara y se presentara una página de inicio de sesión. Además, el sign_out_and_redirect(current_user)
no parece funcionar de la manera que esperaría. Al utilizar la anulación de SessionsController en esa solución, la modifiqué para usar websockets de la siguiente manera:
def create
super
force_logout
end
private
def force_logout
logout_subscribe_address = "signout_subscribe_response_#{current_user[:id]}"
logout_subscribe_resp = {:message => "#{logout_subscribe_address }: #{current_user[:email]} signed out."}
WebsocketRails[:signout_subscribe].trigger(signout_subscribe_address, signout_subscribe_resp)
end
end
Asegúrese de que todas las páginas web se suscriban al canal de suscripción y lo vinculen a la misma acción logout_subscribe_address
. En mi aplicación, cada página también tiene un botón de "cerrar sesión", que cierra la sesión del cliente a través de la sesión Destruir acción de la sesión. Cuando se activa la respuesta de websocket en la página web, simplemente hace clic en este botón: se invoca la lógica de cierre de sesión y se presenta al primer usuario con la página de inicio de sesión.
Esta solución tampoco requiere el skip_before_filter :check_concurrent_session
y el modelo login_token
ya que desencadena el cierre de sesión forzado sin perjuicio.
Para el registro, la devise_security_extension
parece proporcionar la funcionalidad para hacer esto también. También pone una alerta apropiada advirtiendo al primer usuario sobre lo que sucedió (aún no he descubierto cómo hacerlo).
En cuanto a implementarlo realmente en Devise, agregue esto a su modelo User.rb. Algo como esto los desconectará automáticamente (no probado).
def token_valid?
# Use fl00rs method of setting the token
session[:token] == cookies[:token]
end
## Monkey Patch Devise methods ##
def active_for_authentication?
super && token_valid?
end
def inactive_message
token_valid? ? super : "You are sharing your account."
end
Esta gema funciona bien: https://github.com/phatworx/devise_security_extension
Agregar a Gemfile
gem ''devise_security_extension''
después de la instalación del paquete
rails g devise_security_extension:install
Entonces corre
rails g migration AddSessionLimitableToUsers unique_session_id
Edite el archivo de migración
class AddSessionLimitableToUsers < ActiveRecord::Migration
def change
add_column :users, :unique_session_id, :string, limit: 20
end
end
Entonces corre
rake db:migrate
Edite su archivo de aplicaciones / modelos / user.rb
class User < ActiveRecord::Base
devise :session_limitable # other devise options
... rest of file ...
end
Hecho. Ahora iniciar sesión desde otro navegador matará las sesiones anteriores. La gema real notifica al usuario que está a punto de matar una sesión actual antes de iniciar sesión.
Mantenga un registro de direcciones IP uniq utilizadas por usuario. De vez en cuando, realice un análisis de esas IP: el intercambio sería obvio si una sola cuenta tiene inicios de sesión simultáneos de diferentes ISP en diferentes países. Tenga en cuenta que el simple hecho de tener una dirección IP diferente no es motivo suficiente para considerarlo compartido: algunos ISP utilizan proxies round-robin, por lo que cada hit necesariamente sería una dirección IP diferente.
No puedes hacerlo
- Puede controlar las direcciones IP del usuario, por lo que puede evitar la presencia de usuarios de dos IP a la vez. Y puede vincular el inicio de sesión y la IP. Puede intentar consultar las ciudades y otros datos de geolocalización a través de IP para bloquear al usuario.
- Puede establecer cookies para controlar otra cosa.
Pero nada de esto garantizará que solo un usuario use este inicio de sesión, y que esos 105 IP de todo el mundo no pertenezcan a un solo usuario único, que use Proxy o lo que sea.
Y el último: nunca necesitas esto en Internet.
UPD
Sin embargo, lo que estoy preguntando es sobre la limitación de que múltiples usuarios usen la misma cuenta simultáneamente, lo cual creo que debería ser posible.
Así que puedes almacenar algunos tokens, que contendrán algunos datos cifrados: IP + cadena secreta + agente de usuario + versión del navegador del usuario + SO del usuario + cualquier otra información personal: encrypt(IP + "some secret string" + request.user_agent + ...)
. Y luego puede establecer una sesión o cookie con ese token. Y con cada solicitud puede buscarla: si el usuario es el mismo? Está utilizando el mismo navegador y la misma versión de navegador del mismo sistema operativo, etc.
También puedes usar tokens dinámicos: cambias el token a cada solicitud, por lo que solo un usuario puede usar el sistema por sesión, ya que cada token de solicitud se cambiará, otro usuario cerrará sesión hasta que su token expire.
Si bien no puede evitar de manera confiable que los usuarios compartan una cuenta, lo que usted puede hacer (creo) es evitar que más de un usuario inicie sesión al mismo tiempo en la misma cuenta. No estoy seguro si esto es suficiente para su modelo de negocio, pero soluciona muchos de los problemas discutidos en las otras respuestas. Implementé algo que actualmente está en beta y parece funcionar razonablemente bien. here hay algunas notas.