当用户尝试登录时,会话 ID 不仅在本地环境中给出
The session ID is not given only in the local environment when a user tries to log in
即使我没有更改任何代码,登录功能也不起作用,本来可以的。即使我输入了正确的电子邮件地址和密码,它也会被重定向到登录页面。
我不使用设计。
首先,我以为某些编码有误,但根据日志,它说电子邮件地址和密码是正确的,并尝试正确重定向到首页。
接下来,我使用了工具 Burp Suite
来跟踪浏览器和服务器(本地代理)之间的会话 ID 等数据。
并且发现登录后没有给session ID,又带来了authenticate_user
导致的错误,导致登录失败。
我想找出原因并解决。
此外,我还使用了工具ngrok
,让用户可以访问我的本地主机。
然后令人惊讶的是,我本地环境之外的用户能够成功登录。
并且 Burp Suite
表明会话 ID 在这种情况下被赋予 属性。
因此,只有当我尝试从我的本地环境登录时,才不会提供会话 ID。
最近,我没有编辑任何代码,而是完成了以下这些任务。
- 安装Git.
- 通过Git将我的“app1”推送到GitHub。
- 安装 SourseTree,并连接到我的 GitHub。
- 通过 SourceTree 从 GitHub 克隆我的“app1”,并将其命名为“app2”。
在执行这些任务之前,我在下面添加了这段代码。但是即使我注释掉它也不会改变这种情况。
application.rb
config.session_store :cookie_store, expire_after: 8.hours
所有问题都发生在这些任务之后。
代码
routes.rb
get "login" => "users#login_form"
post "login" => "users#login"
post "logout" => "users#logout"
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
before_action :set_current_user
skip_before_action :verify_authenticity_token
caches_action :set_current_user, :authenticate_user, :forbid_login_user
def set_current_user
@current_user = User.find_by(id: session[:user_id])
end
def authenticate_user
if @current_user == nil
flash[:notice] = "You need to log in"
redirect_to("/login")
end
end
def forbid_login_user
if @current_user
flash[:notice] = "You are already logged in"
redirect_to("/")
end
end
end
users_controller.rb
before_action :authenticate_user, {except: [:new, :create, :login_form, :login]}
before_action :forbid_login_user, {only: [:new, :create, :login_form, :login]}
before_action :ensure_correct_user, {only: [:edit, :update]}
before_action :ensure_correct_user_account, {only: [:setting_password, :update_password, :setting_email, :update_email, :delete_account, :destroy]}
caches_action :index, :show, :follow, :new, :create, :edit, :user_params, :update, :destroy, :login_form, :login, :logout, :likes, :ensure_correct_user, :followings, :followers, :top
def login
@user = User.find_by(email: params[:email])
if @user && @user.authenticate(params[:password])
session[:user_id] = @user.id
flash[:notice] = "You have successfully logged in"
redirect_to("/")
else
@error_message = "Email address or password is incorrect"
@email = params[:email]
@password = params[:password]
render("users/login_form")
end
end
def login_form
end
def logout
session[:user_id] = nil
flash[:notice] = "You have successfully logged out"
redirect_to("/login")
end
当用户成功登录后,重定向到“/”(首页)。
日志
这是我尝试登录时的日志,看到Started GET "/"
,用户似乎登录成功了。但随后日志显示 Filter chain halted as :authenticate_user rendered or redirected
,并重定向到 "/login"
.
Started POST "/login" for ::1 at 2021-08-24 14:44:31 +0900
Processing by UsersController#login as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"JBcKtTsdCT0JXgyIfzjKVqi/8KEz4pRmUXB2Kybn8eHcnz7UKXoMsbBRkBCnoUqwnIdi4hUkZ/6oQKFVqMyG/g==", "email"=>"hoge", "password"=>"[FILTERED]"}
User Load (35.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/controllers/application_controller.rb:9
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`email` = 'hoge' LIMIT 1
↳ app/controllers/users_controller.rb:255
Redirected to http://localhost:3000/
Completed 302 Found in 422ms (ActiveRecord: 41.9ms)
Started GET "/" for ::1 at 2021-08-24 14:44:32 +0900
Processing by UsersController#top as HTML
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/controllers/application_controller.rb:9
Redirected to http://localhost:3000/login
Filter chain halted as :authenticate_user rendered or redirected
Completed 302 Found in 6ms (ActiveRecord: 0.6ms)
Started GET "/login" for ::1 at 2021-08-24 14:44:32 +0900
Processing by UsersController#login_form as HTML
User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/controllers/application_controller.rb:9
Rendering users/login_form.html.erb within layouts/application
Rendered users/login_form.html.erb within layouts/application (0.9ms)
CACHE User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/views/layouts/application.html.erb:51
Completed 200 OK in 244ms (Views: 233.9ms | ActiveRecord: 0.5ms)
Burp Suite 的展示
我使用ngrok
时用户从外部登录时会话ID是如何给出的。您可以看到同时显示了“Cookie”和“Set-Cookie”。
当我尝试从本地环境登录时。 “Cookie”和“Set-Cookie”都看不到。
我试过的
重启服务器和我的电脑都没有解决问题。
多个浏览器(Google Chrome、Firefox 和 Brave)显示相同的结果。
版本
ruby 2.6.4p104
红宝石宝石 3.0.3
Rails5.2.3
Burp Suite 社区版 2021.8.2
后记
这些作品并没有改变这种情况。
- 将
bundle exec rake tmp:cache:clear
放在命令上。
- 将
rails tmp:clear
放在命令上。
- 删除
users_controller.rb
中的会话历史记录
def login_form
reset_session
session[:user_id] = nil
session.delete(:user_id)
end
更好的身份验证方法是:
# app/errors/authentication_error.rb
class AuthenticationError < StandardError; end
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
before_action :authenticate_user!
rescue_from 'AuthenticationError', with: :deny_access
private
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def user_signed_in?
current_user.present?
end
def authenticate_user!
raise AuthenticationError unless user_signed_in?
end
def deny_access
flash[:notice] = "You need to log in"
redirect_to login_path
end
end
不要使用回调来设置当前用户,而是使用记忆其值的方法。这消除了与回调顺序相关的问题的可能性。这应该是唯一知道用户如何在会话中持久化的方法。
使用默认安全方法而不是显式身份验证。这意味着您不会调用 before_action :authenticate_user, {except: [:new, :create, :login_form, :login]}
,而是调用 skip_before_action :authenticate_user, only: [:new, :create, :login_form, :login]
来选择退出授权。
如果用户未通过身份验证则引发错误将在测试时为您节省大量调试时间,因为您可以禁用 rescue_from
以确保引发错误。
与其将所有内容都塞进一个怪物 UsersController 中,不如创建一个控制器,它只负责让用户登录和注销:
scope controller: 'sessions' do
get :login, action: :new, as: :login
post :login, action: :create
delete :logout, action: :destroy, as: :logout
get :logout, action: :destroy, as: :logout
end
class SessionsController < ApplicationController
skip_before_action :authenticate_user!
before_action :deny_access_to_signed_in_users, if: :user_signed_in?,
# GET /login
def new
@user = User.new
end
# POST /login
def create
@user = User.find_by(email: params[:email])
if @user && @user.authenticate(params[:password])
session[:user_id] = @user.id
flash[:notice] = "You have successfully logged in"
redirect_to root_path
else
flash[:notice] = "Email address or password is incorrect"
@user = User.new(**params.permit(:email, :password))
render :new
end
end
# DELETE /logout
def destroy
session[:user_id] = nil
flash[:notice] = "You have successfully logged out"
redirect_to login_path
end
private
def deny_access_to_signed_in_users
flash[:notice] = "You are already logged in!"
redirect_to root_path
end
end
# app/views/sessions/new.html.erb
<h1>Sign in</h1>
<%= form_with(model: @user, path: login_path, method: :post, local: true) do |form| %>
<div class="field">
<%= form.label :email %>
<%= form.email_field :email %>
</div>
<div class="field">
<%= form.label :password %>
<%= form.email_field :email %>
</div>
<div class="actions">
<%= form.submit 'Sign in' %>
</div>
<% end %>
我能够自己解决问题。
我以前做的文件session_store.rb
,就是这个原因
配置 > 初始值设定项 > session_store.rb
app_name = "app_name"
Rails.application.config.session_store :cookie_store, key: "_app_name_session", expire_after: 8.hours, secure: true
secure: true
禁用 http 传递 cookie,所以我无法仅在本地主机上登录。我忘了删除这个文件。
我删除了session_store.rb
,在application.rb
写了下面的代码。
config.session_store :cookie_store, expire_after: 8.hours, secure: Rails.env.production?
现在,登录功能也适用于本地主机。
即使我没有更改任何代码,登录功能也不起作用,本来可以的。即使我输入了正确的电子邮件地址和密码,它也会被重定向到登录页面。 我不使用设计。
首先,我以为某些编码有误,但根据日志,它说电子邮件地址和密码是正确的,并尝试正确重定向到首页。
接下来,我使用了工具 Burp Suite
来跟踪浏览器和服务器(本地代理)之间的会话 ID 等数据。
并且发现登录后没有给session ID,又带来了authenticate_user
导致的错误,导致登录失败。
我想找出原因并解决。
此外,我还使用了工具ngrok
,让用户可以访问我的本地主机。
然后令人惊讶的是,我本地环境之外的用户能够成功登录。
并且 Burp Suite
表明会话 ID 在这种情况下被赋予 属性。
因此,只有当我尝试从我的本地环境登录时,才不会提供会话 ID。 最近,我没有编辑任何代码,而是完成了以下这些任务。
- 安装Git.
- 通过Git将我的“app1”推送到GitHub。
- 安装 SourseTree,并连接到我的 GitHub。
- 通过 SourceTree 从 GitHub 克隆我的“app1”,并将其命名为“app2”。
在执行这些任务之前,我在下面添加了这段代码。但是即使我注释掉它也不会改变这种情况。
application.rb
config.session_store :cookie_store, expire_after: 8.hours
所有问题都发生在这些任务之后。
代码
routes.rb
get "login" => "users#login_form"
post "login" => "users#login"
post "logout" => "users#logout"
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
before_action :set_current_user
skip_before_action :verify_authenticity_token
caches_action :set_current_user, :authenticate_user, :forbid_login_user
def set_current_user
@current_user = User.find_by(id: session[:user_id])
end
def authenticate_user
if @current_user == nil
flash[:notice] = "You need to log in"
redirect_to("/login")
end
end
def forbid_login_user
if @current_user
flash[:notice] = "You are already logged in"
redirect_to("/")
end
end
end
users_controller.rb
before_action :authenticate_user, {except: [:new, :create, :login_form, :login]}
before_action :forbid_login_user, {only: [:new, :create, :login_form, :login]}
before_action :ensure_correct_user, {only: [:edit, :update]}
before_action :ensure_correct_user_account, {only: [:setting_password, :update_password, :setting_email, :update_email, :delete_account, :destroy]}
caches_action :index, :show, :follow, :new, :create, :edit, :user_params, :update, :destroy, :login_form, :login, :logout, :likes, :ensure_correct_user, :followings, :followers, :top
def login
@user = User.find_by(email: params[:email])
if @user && @user.authenticate(params[:password])
session[:user_id] = @user.id
flash[:notice] = "You have successfully logged in"
redirect_to("/")
else
@error_message = "Email address or password is incorrect"
@email = params[:email]
@password = params[:password]
render("users/login_form")
end
end
def login_form
end
def logout
session[:user_id] = nil
flash[:notice] = "You have successfully logged out"
redirect_to("/login")
end
当用户成功登录后,重定向到“/”(首页)。
日志
这是我尝试登录时的日志,看到Started GET "/"
,用户似乎登录成功了。但随后日志显示 Filter chain halted as :authenticate_user rendered or redirected
,并重定向到 "/login"
.
Started POST "/login" for ::1 at 2021-08-24 14:44:31 +0900
Processing by UsersController#login as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"JBcKtTsdCT0JXgyIfzjKVqi/8KEz4pRmUXB2Kybn8eHcnz7UKXoMsbBRkBCnoUqwnIdi4hUkZ/6oQKFVqMyG/g==", "email"=>"hoge", "password"=>"[FILTERED]"}
User Load (35.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/controllers/application_controller.rb:9
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`email` = 'hoge' LIMIT 1
↳ app/controllers/users_controller.rb:255
Redirected to http://localhost:3000/
Completed 302 Found in 422ms (ActiveRecord: 41.9ms)
Started GET "/" for ::1 at 2021-08-24 14:44:32 +0900
Processing by UsersController#top as HTML
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/controllers/application_controller.rb:9
Redirected to http://localhost:3000/login
Filter chain halted as :authenticate_user rendered or redirected
Completed 302 Found in 6ms (ActiveRecord: 0.6ms)
Started GET "/login" for ::1 at 2021-08-24 14:44:32 +0900
Processing by UsersController#login_form as HTML
User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/controllers/application_controller.rb:9
Rendering users/login_form.html.erb within layouts/application
Rendered users/login_form.html.erb within layouts/application (0.9ms)
CACHE User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1
↳ app/views/layouts/application.html.erb:51
Completed 200 OK in 244ms (Views: 233.9ms | ActiveRecord: 0.5ms)
Burp Suite 的展示
我使用ngrok
时用户从外部登录时会话ID是如何给出的。您可以看到同时显示了“Cookie”和“Set-Cookie”。
当我尝试从本地环境登录时。 “Cookie”和“Set-Cookie”都看不到。
我试过的
重启服务器和我的电脑都没有解决问题。 多个浏览器(Google Chrome、Firefox 和 Brave)显示相同的结果。
版本
ruby 2.6.4p104 红宝石宝石 3.0.3 Rails5.2.3 Burp Suite 社区版 2021.8.2
后记
这些作品并没有改变这种情况。
- 将
bundle exec rake tmp:cache:clear
放在命令上。 - 将
rails tmp:clear
放在命令上。 - 删除
users_controller.rb
中的会话历史记录
def login_form
reset_session
session[:user_id] = nil
session.delete(:user_id)
end
更好的身份验证方法是:
# app/errors/authentication_error.rb
class AuthenticationError < StandardError; end
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
before_action :authenticate_user!
rescue_from 'AuthenticationError', with: :deny_access
private
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def user_signed_in?
current_user.present?
end
def authenticate_user!
raise AuthenticationError unless user_signed_in?
end
def deny_access
flash[:notice] = "You need to log in"
redirect_to login_path
end
end
不要使用回调来设置当前用户,而是使用记忆其值的方法。这消除了与回调顺序相关的问题的可能性。这应该是唯一知道用户如何在会话中持久化的方法。
使用默认安全方法而不是显式身份验证。这意味着您不会调用 before_action :authenticate_user, {except: [:new, :create, :login_form, :login]}
,而是调用 skip_before_action :authenticate_user, only: [:new, :create, :login_form, :login]
来选择退出授权。
如果用户未通过身份验证则引发错误将在测试时为您节省大量调试时间,因为您可以禁用 rescue_from
以确保引发错误。
与其将所有内容都塞进一个怪物 UsersController 中,不如创建一个控制器,它只负责让用户登录和注销:
scope controller: 'sessions' do
get :login, action: :new, as: :login
post :login, action: :create
delete :logout, action: :destroy, as: :logout
get :logout, action: :destroy, as: :logout
end
class SessionsController < ApplicationController
skip_before_action :authenticate_user!
before_action :deny_access_to_signed_in_users, if: :user_signed_in?,
# GET /login
def new
@user = User.new
end
# POST /login
def create
@user = User.find_by(email: params[:email])
if @user && @user.authenticate(params[:password])
session[:user_id] = @user.id
flash[:notice] = "You have successfully logged in"
redirect_to root_path
else
flash[:notice] = "Email address or password is incorrect"
@user = User.new(**params.permit(:email, :password))
render :new
end
end
# DELETE /logout
def destroy
session[:user_id] = nil
flash[:notice] = "You have successfully logged out"
redirect_to login_path
end
private
def deny_access_to_signed_in_users
flash[:notice] = "You are already logged in!"
redirect_to root_path
end
end
# app/views/sessions/new.html.erb
<h1>Sign in</h1>
<%= form_with(model: @user, path: login_path, method: :post, local: true) do |form| %>
<div class="field">
<%= form.label :email %>
<%= form.email_field :email %>
</div>
<div class="field">
<%= form.label :password %>
<%= form.email_field :email %>
</div>
<div class="actions">
<%= form.submit 'Sign in' %>
</div>
<% end %>
我能够自己解决问题。
我以前做的文件session_store.rb
,就是这个原因
配置 > 初始值设定项 > session_store.rb
app_name = "app_name"
Rails.application.config.session_store :cookie_store, key: "_app_name_session", expire_after: 8.hours, secure: true
secure: true
禁用 http 传递 cookie,所以我无法仅在本地主机上登录。我忘了删除这个文件。
我删除了session_store.rb
,在application.rb
写了下面的代码。
config.session_store :cookie_store, expire_after: 8.hours, secure: Rails.env.production?
现在,登录功能也适用于本地主机。