User sessions invalid after changing password, but only with multiple threads

半腔热情 提交于 2019-12-05 14:37:58

Since you're using Dalli as your session store you may be running up against this issue.

Multithreading Dalli

From the page:

"If you use Puma or another threaded app server, as of Dalli 2.7, you can use a pool of Dalli clients with Rails to ensure the Rails.cache singleton does not become a source of thread contention."

I suspect you're seeing that behavior due to the following issues:

  • devise defines the current_user helper method using an instance variable getting the value from warden. in lib/devise/controllers/helpers.rb#58. Substitute user for mapping

    def current_#{mapping}
      @current_#{mapping} ||= warden.authenticate(:scope => :#{mapping})
    end
    

Not having run into this myself, this is speculation, but hopefully it's helpful in some way. In a multi-threaded app, each request is routed to a thread which may be keeping the previous value of the current_user around due to caching, either in thread local storage or rack which may track data per thread.

One thread changes the underlying data (the password change), invalidating the previous data. The cached data shared among other threads is not updated, causing later accesses using the stale data to cause the forced logout. One solution might be to flag that the password changed, allowing the other threads to detect that change and handle it gracefully, without a forced logout.

I would suggest that after a user changes their password, log them out and clear their sessions, like so:

  def update_password
    @user = User.find(current_user.id)
    if @user.update(user_params)
      sign_out @user # Let them sign-in again
      reset_session # This might not be needed?
      redirect_to root_path
    else
      render "edit"
    end
  end

I believe your main issue is the way that sign_in updates the session combined with the multi-threads as you mentioned.

This is a gross, gross solution, but it appeared that the other threads would do ActiveRecord query caching of my User model, and the stale data returned would trigger an authentication failure.

By adapting a technique described in Bypassing ActiveRecord cache, I added the following to my User.rb file:

# this default scope avoids query caching of the user,
# which can be a big problem when multithreaded user password changing
# happens. 
FIXNUM_MAX = (2**(0.size * 8 -2) -1)
default_scope { 
  r = Random.new.rand(FIXNUM_MAX)
  where("? = ?", r,r)
}

I realize this has performance implications that pervade throughout my application, but it seems to be the only way I could get around the issue. I tried overriding many of the devise and warden methods which use this query, but without luck. Perhaps I'll look into filing a bug against devise/warden soon.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!