diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index 3de68a4..c1e3b9c 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -174,3 +174,16 @@ input { } } +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 1fff20c..bf8fe07 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -8,7 +8,7 @@ class SessionsController < ApplicationController # if user && user.authenticate(params[:session][:password]) if user&.authenticate(params[:session][:password]) reset_session - remember user + params[:session][:remember_me] == '1' ? remember(user) : forget(user) log_in user redirect_to user else diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index b7cf36c..fc1dcf2 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -1,6 +1,8 @@ module SessionsHelper def log_in(user) session[:user_id] = user.id + # 防范会话重放攻击 + session[:session_token] = user.session_token end def remember(user) @@ -11,7 +13,10 @@ module SessionsHelper def current_user if (user_id = session[:user_id]) - @current_user ||= User.find_by(id: user_id) + user = User.find_by(id: user_id) + if user && session[:session_token] == user.session_token + @current_user = user + end elsif (user_id = cookies.encrypted[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) diff --git a/app/models/user.rb b/app/models/user.rb index 2e83727..aec527c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -23,6 +23,13 @@ class User < ApplicationRecord def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) + remember_digest + end + + # 返回一个会话令牌,防止会话劫持 + # 简单起见,直接使用记忆令牌 + def session_token + remember_digest || remember end class << self diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index e19ea07..bd4fe03 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -10,6 +10,11 @@ <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> + <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me on this computer + <% end %> + <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> diff --git a/test/helpers/sessions_helper_test.rb b/test/helpers/sessions_helper_test.rb new file mode 100644 index 0000000..500b3b8 --- /dev/null +++ b/test/helpers/sessions_helper_test.rb @@ -0,0 +1,19 @@ +require 'test_helper' + +class SessionsHelperTest < ActionView::TestCase + + def setup + @user = users(:michael) + remember(@user) + end + + test "current_user returns right user when session is nil" do + assert_equal @user, current_user + assert is_logged_in? + end + + test "current_user returns nil when remember digest is wrong" do + @user.update_attribute(:remember_digest, User.digest(User.new_token)) + assert_nil current_user + end +end \ No newline at end of file diff --git a/test/integration/users_login_test.rb b/test/integration/users_login_test.rb index 818f3e5..46cfb8b 100644 --- a/test/integration/users_login_test.rb +++ b/test/integration/users_login_test.rb @@ -50,4 +50,15 @@ class UsersLoginTest < ActionDispatch::IntegrationTest assert_select "a[href=?]", logout_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 end + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + log_in_as(@user, remember_me: '1') + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 45f3e7e..93c8556 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -17,6 +17,16 @@ module ActiveSupport !session[:user_id].nil? end - # Add more helper methods to be used by all tests here... + # def log_in_as(user) + # session[:user_id] = user.id + # end + end + + class ActionDispatch::IntegrationTest + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end end end