ror:高级登录功能

切换分支

git checkout log-in

记忆令牌

生成迁移

rails generate migration add_remember_digest_to_users remember_digest:string

执行迁移

rails db:migrate

生成随机令牌

# app/models/User.rb

... ...
    attr_accessor :remember_token
... ...
    def User.new_token
        SecureRandom.urlsafe_base64
    end

    # 生成记忆令牌,写入数据库(跳过认证)
    def remember
        self.remember_token = User.new_token
        update_attribute(:remember_digest, User.digest(remember_token))  # update_attribute会跳过认证阶段(不知道密码)
    end
... ...

登录记住状态

认证令牌

# app/models/user.rb

... ...
    def authenticated?(remember_token)
        BCrypt::Password.new(remember_digest).is_password?(remember_token)
    end
... ...

辅助方法

# app/helpers/sessions_helper.rb

... ...

    # 记住登录状态
    def remember(user)
        user.remember
        cookies.permanent.signed[:user_id] = user.id
        # signed: 签名
        cookies.permanent[:remember_token] = user.remember_token
        # cookies[:remember_token] = { value: user.remember_token, expires: 20.years.from_now.utc }
    end

    # 返回当前登录用户
    def current_user
        if (user_id = session[:user_id])
            @current_user ||= User.find_by(id: user_id)
        elsif (user_id = cookies.signed[:user_id])
            user = User.find_by(id: user_id)
            if user && user.authenticated?(cookies[:remember_token])
                log_in user
                @current_user = user
            end
        end
    end
... ...

记住登录状态

# app/controllers/sessions_controller.rb

... ...
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      remember user
... ...

忘记用户

# app/models/user.rb

... ...
    # 忘记用户
    def forget
        update_attribute(:remember_digest, nil)
    end
... ...

辅助函数

# app/helpers/sessions_helper.rb

... ...

    # 忘记用户
    def forget(user)
        user.forget
        cookies.delete(:user_id)
        cookies.delete(:remember_token)
    end

    # 退出当前用户
    def log_out
        forget(current_user)
        session.delete(:user_id)
        @current_user = nil
    end
... ...

解决问题

多个窗口间退出

编写测试:

# test/integration/users_login_test.rb

... ...
  test "login with valid information followed by logout in multi-windows" do
    get login_path
    post login_path, session: { email: @user.email, password: 'joshua' }
    assert is_logged_in?
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
    delete logout_path
    assert_not is_logged_in?
    assert_redirected_to root_url
    # 模拟用户在另一个窗口点击退出
    delete logout_path
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path, count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
... ...

修改退出方法:

# app/controllers/sessions_controller.rb

... ...
  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
... ...

多浏览器间退出

编写测试:

# test/models/user_test.rb

... ...
  test "authenticated? should return false for a user with nil digest" do
    assert_not @user.authenticated?('')
  end
... ...

修改认证方法:

# app/models/user.rb

... ...
    def authenticated?(remember_token)
        return false if remember_digest.nil?
        BCrypt::Password.new(remember_digest).is_password?(remember_token)
    end
... ...

Remember Me

添加复选框

# app/views/sessions/new.html.erb

... ...
            <%= f.label :remember_me, class: "checkbox inline" do %>
                <%= f.check_box :remember_me %>
                Remember me?
            <% end %>
            <%= f.submit "Log in", class: "btn btn-primary" %>
... ...

修改样式

# app/assets/stylesheets/custom.css.scss

... ...
.checkbox {
    margin-top: -10px;
    margin-bottom: 10px;
    span {
        margin-left: 20px;
        font-weight: normal;
    }
}
#session_remember_me {
    width: auto;
    margin-left: 0;
}
ror:高级登录功能_第1张图片

实现功能

# app/controllers/sessions_controller.rb

... ...
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      # remember user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
... ...

测试

编写辅助功能

# test/test_helper.rb

... ...
  # 登录测试用户
  def log_in_as(user, options = {})
    password = options[:password] || 'joshua'
    remember_me = options[:remember_me] || '1'
    if integration_test?
      post login_path, session: { email: user.email, password: password, remember_me: remember_me }
    else
      session[:user_id] = user.id
    end
  end

private
  # 在集成测试中返回true
  def integration_test?
    defined?(post_via_redirect)
  end
... ...

编写测试

# test/integration/users_login_test.rb

... ...
  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_not_nil cookies['remember_token']
  end

  test "login without remembering" do
    log_in_as(@user, remember_me: '0')
    assert_nil cookies['remember_token']
  end
... ...

合并删除分支

git checkout master
git merge log-in
git branch -d log-in
git branch

部署

heroku maintenance:on  # 开启维护模式
git push heroku master
heroku rake db:migrate
heroku maintenance:off

参考

《Rails Tutorial 3th》

你可能感兴趣的:(ror:高级登录功能)