🍣

【Rails】アクセス制限

2023/06/26に公開

アクセス制限の目的

パスやルーティングが見えなくとも、URLを予測し、別のユーザー情報にアクセスすることができます。
データを保護し、不正なアクセスから守るためにもアクセス制限は必要です。

データを表示させない仕組み

別ユーザーからアクセスされないよう、以下のフローのように制限します。

現在ログインしているユーザーかどうかの判断は、current_userparams[:id]いうメソッドを使用します。それぞれについて解説します。

current_userとは

current_userメソッドはDeviseのヘルパーメソッドです。簡単に言えば、このメソッドを使えば、現在ログインしているユーザーの情報を取得することができます。
以下の記事でも解説していますので参考にしてもらえたら嬉しいです。
https://zenn.dev/ganmo3/articles/828b884148b74c

細かい仕組みは次の通りです。

current_userメソッドの仕組み
  1. セッションと認証
    Webアプリケーションでは、ユーザーがログインするとセッション(ユーザー情報を一時的に保存する仕組み)が開始され、ユーザーの情報がサーバー側に保存されます。このセッションを通じて、アプリケーションはユーザーが誰であるかを識別し、適切なアクセス制御やパーソナライズされた機能を提供することができます。

  2. セッションとユーザー情報の関連付け
    ユーザーがログインするとき、一般的なフローは次の通りです。

  • ユーザーが認証情報(ユーザー名とパスワードなど)を入力してログインフォームを送信。
  • サーバー側では、入力された認証情報を検証し、正しい場合はセッションにユーザー情報を格納。
  • サーバーは、セッションIDをブラウザにクッキー(ウェブサイトがユーザーのブラウザに保存する情報のひとつ)として送信。これにより、ブラウザとサーバーの間でセッションが維持される。
  • 以降のリクエストでは、ブラウザから送信されるセッションIDを使用して、サーバーはセッションを特定し、関連するユーザー情報を取得。
  1. current_userの定義と利用
    current_userは、セッションを通じて特定のユーザーを識別するための便利な方法です。Railsでは、セッションからユーザー情報を取り出し、current_userに格納することで、アプリケーション内のどこからでも現在のユーザーを簡単にアクセスできるようになります。
余談:セッションとクッキーとは

"セッション"と"クッキー"について、自分の理解のために例を交えて説明します。

セッション
例えるならば、ホテルのルームキー(カードキー)のようなもの。ホテルに滞在する際、部屋に入るためのルームキーが渡される。このルームキーは一時的なものであり、滞在期間が終わると無効になる。セッションも同様で、ウェブサイトとのやり取りにおいて一時的な情報や状態を管理するための仕組み。セッションはサーバー側によって発行され、ブラウザ(クライアント)に個別の識別情報が与えられる。

クッキー
例えるならば、ポイントカードのようなもの。ポイントカードは常に持ち歩き、個人の情報や特典の履歴などが保存されている。ポイントカードを提示することで、特典を受けたりポイントを貯めたりすることができる。同様に、クッキーはブラウザに保存される小さなファイルであり、ユーザーの個人情報やウェブサイトの訪問履歴などが保存される。ウェブサイトはクッキーを使用してユーザーを識別し、特定の設定やパーソナライズされた情報を提供することができる。

params[:id]とは

例えば、Webブラウザでユーザーのプロフィールページにアクセスすると、そのページのURLは/users/1のようになります。ここで、:idには1という値が入ります。params[:id]は、URL内の:id部分の値を取得するためのコードです。これにより、アプリケーションは特定のユーザーを識別し、関連するデータを取得したり、必要な処理を行ったりすることができます。

具体的には、コントローラーでparams[:id]を使用することで、その値を利用して特定のユーザーのプロフィール情報を表示したり、編集したりすることができます。データベースのテーブル内のレコードを識別するために、ユーザーIDなどが一般的に使用されます。

簡単に言えば、params[:id]はURL内の特定の値を取得するためのコードであり、その値に基づいてアプリケーションが必要な処理を行います。

実装

実装の流れは次の通りです。

  1. URLに含まれるユーザーidをparams[:id]で取得。
  2. ログインしているユーザーのidをcurrent_user.idで取得。
  3. 1と2のidが一致していなかった場合、 投稿一覧にリダイレクトする。

例としてユーザー情報の編集画面と更新処理に対して実装をします。

app/controllers/users_controller.rb
class UsersController < ApplicationController
:
:
def edit
+ user = User.find(params[:id])
+ unless user.id == current_user.id
+   redirect_to user_path(current_user)
+ end
  
  @user = User.find(params[:id])
end

def update
+ user = User.find(params[:id])
+ unless user.id == current_user.id
+   redirect_to user_path(current_user)
+ end

  @user = User.find(params[:id])
  if @user.update(user_params)
    flash[:notice] = "You have updated user successfully."
    redirect_to user_path(current_user)
  else
    render :edit
  end
end
:
:

メソッドで処理をまとめる

editとupdateに追加したコードは全く同じです。同じ処理を複数のアクションで実行する場合は、コードの重複を避けるために、メソッドとしてまとめることができます。これにより、コードの量を削減し、読みやすいコードにすることができます。

具体的には、ensure_correct_userというメソッドを作成します。このメソッドは、controller内でのみ使用するため、privateブロック内に記述します。

private

  def user_params
    params.require(:user).permit(:name, :introduction, :profile_image)
  end

+ def ensure_correct_user
+   user = User.find(params[:id])
+   unless user.id == current_user.id
+     redirect_to user_path(current_user)
+   end
end

上記のようにメソッドを定義しました。その後、editとupdateアクション内でこのメソッドを呼び出すことができます。

def edit
+ ensure_correct_user
  @user = User.find(params[:id])
end

def update
+ ensure_correct_user
  @user = User.find(params[:id])
  # その他の処理
end

このようにすることで、重複するコードを削減し、コードの保守性と可読性を向上させることができます。メソッドを利用することで、同じ処理を複数の場所で使い回すことができます。

before_actionについて

メソッドとして処理をまとめとことにより、before_actionを使用できます。このメソッドはコントローラー内のアクション(メソッド)の実行前に別のメソッドを実行するためのフィルターです。これにより、共通の処理を実行したり、アクションの実行条件をチェックしたりすることができます。

berore_actionの記述方法

基本的な構文は以下の通りです。

class UsersController < ApplicationController
  before_action :method_name, options

  # アクションの定義とその他のコード
end

before_actionは、:method_nameの部分に実行したいメソッドの名前を指定します。また、:optionsにはオプションを指定することができます。

一般的なオプションとしては、:only:exceptがよく使われます。

  • :only
    onlyを使用すると、指定したアクションの前でのみフィルターが実行されます。複数のアクションを指定する場合は配列で指定します。
before_action :method_name, only: [:action1, :action2]
  • :except
    exceptを使用すると、指定したアクション以外のアクションの前でフィルターが実行されます。同じく複数のアクションを指定する場合は配列で指定します。
before_action :method_name, except: [:action1, :action2]

複数のオプションを同時に指定することもできます。

before_action :method_name, only: [:action1], except: [:action2]

before_actionの実装

具体的には、以下のようにbefore_actionを使用してensure_correct_userメソッドを呼び出します。

app/controllers/users_controller.rb
class UsersController < ApplicationController
+ before_action :ensure_correct_user, only: [:update, :edit]
  # 他のコード
  
def edit
  @user = User.find(params[:id])
end

def update
  @user = User.find(params[:id])
  # その他の処理
end

private

  def user_params
    params.require(:user).permit(:name, :introduction, :profile_image)
  end

 def ensure_correct_user
   user = User.find(params[:id])
   unless user.id == current_user.id
     redirect_to user_path(current_user)
   end
end

上記のようにbefore_actionを使用することで、"edit"と"update"アクションの前にensure_correct_userメソッドが呼び出されます。これにより、重複したコードを避けつつ、必要な処理を実行することができます。

before_action:onlyオプションを使用することで、どのアクションの前でフィルターを実行するかを指定します。この例では、[:update, :edit]と指定することで、"update"と"edit"アクションの前でensure_correct_userメソッドが実行されます。

これにより、コードの重複を避けつつ、DRY原則(Don't Repeat Yourself)に従ったコードになります。


今日は息子が熱を出したため、保育園を休み、1日ずっと一緒に過ごしました。
なので投稿が日を超えるぎりぎりの時間になりました。
子どもがいるといつ体調を崩してしまうか本当にわからないですね💦
5月には胃腸炎で1週間休ませなければならなくなり、今月はRSウイルスとコロナでまた1週間休ませなければならなかったので、最近は心配が多かったです。

ただ、今日は息子と一日中一緒に過ごせたので、本当に楽しかったし幸せでした。昼以降は元気になってきたので、少しお散歩したり、絵本を読んだり、一緒にいないいないばあして遊んだり♪
何が起こるかわからないので、勉強できる時には集中して頑張ろうと思います。
子育て中は予期せぬことが起こることが多いですが、大切なのはその時々を大切に過ごすことだと思いました。
息子との時間を楽しみ、勉強の時間も充実させていけるようにしていきます!

Discussion