11
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

URL直打ち対策(ログアウト/ログイン別まとめ)

Posted at

#はじめに
アプリ実装中、"URL直打ち"対策を行なったので、記録として残します。

Ruby 2.6.5

#目次
1. "URL直打ち"とは
2. 対 ログアウトユーザー
3. 対 ログインユーザー
4. 対 ログインしている自分
5. まとめ

#1. "URL直打ち"とは
"URL直打ち"とは、その名前の通りURLに直接書き込むことです。

例えば、他のユーザーが投稿したつぶやきや写真・動画などはこちらが勝手に編集・削除はできませんよね。
それに対し、自分が投稿したものに対してはいつでも編集・削除ができます。
これは自分自身と他のユーザーとで画面上でのボタンの表示の切り替えがされるように実装されているからです。
なので、普通なら他ユーザーの投稿は編集・削除ができません。

しかし、URLの仕組みを理解してると、URLに直接書き込むことで他のユーザーの編集画面ページに遷移することができてしまいます!

なので、この対策はしっかりおこなわなければなりません!
それが"URL直打ち"対策というわけです。

今回登場するモデルとテーブルの関係

モデルとテーブルの関係を簡単にではありますが、記載しておきます。
スライド1 (6).JPG

#2. 対 ログアウトユーザー
まずは、ログアウトユーザーへの対策です。

これはすごくシンプルで、以下のコードを対策したいコントローラー内に設定していれば対策できています!

  before_action :authenticate_user!, except: [:index, :show]

実際にコントローラー内に記述したらこんな感じ。
items_controller.rb
class ItemsController < ApplicationController
  before_action :authenticate_user!, except: [:index, :show]

  def index
  end

  def show
  end
end

記述したコードを1つずつ要約すると以下の通りです。

記述 説明
before_action このコードの後ろに書かれたメソッドは、このコントローラー内の処理が動く前に実行される.
つまり、一番はじめに実行される.
:authenticate_user! ログイン済みユーザーの認証を行う。
ログインしてなければ、ログイン画面にとばす!
devise機能を実装することで使えるメソッドです。
(作成するモデル名によってuserの部分が変わります。今回はモデル名にUserを使用しています)
except: [:index, :show] index(一覧)とshow(詳細)は除く
これを踏まえて、このコードを翻訳すると

「ログインしてなかったら弾き飛ばす。でも、indexとshowはログインしてなくても見れるよ。」

というような感じです。
大体の投稿アプリは、以下のような機能はログアウトしていてもできると思います。

  • 他の投稿者のツイートや写真・動画投稿の閲覧
  • その投稿者の詳細ページ閲覧
  • 検索

しかし、いざ投稿に対してのコメントや自分が投稿しようとしたらログイン画面に飛ばされてしまいますよね?
それはこの対策がしっかりされているからです。
もちろん、URLを直打ちしてページ遷移をすることも防いでくれています!

ちなみに、exceptはその後に指定されたものを「除く」という意味ですが、これと反対でonlyというものもがあります。
こちらは、その後に指定したもの「のみ対象」という意味になります。

  before_action :authenticate_user!, only: [:index, :show]
  # indexとshowのみログアウトしてたら見れないよ

これで、対ログアウトユーザーへの対策はできました!

3. 対 ログインユーザー

次はログインしている自分以外のユーザーに対しての対策です。

例えば自身が投稿した内容はいつでも編集(edit, update)・削除(destroy)できますよね。
画面上ではログイン/ログアウト、自身と他ユーザーによってボタン表示の切り替えはバッチリできていても、URLの対策をしていないと直接URLに打ち込まれてあっさり編集画面に侵入...なんてことが起きてしまいます。
しっかり対策しておきましょう!

他にも色々あると思いますが、シンプルな例として以下のことが挙げられます。

  • 自身が投稿(出品)した内容を他ユーザーに編集・削除されたくない

これに対しては以下のようにコードを記述することで対策ができます。

  # 商品情報のidを取得
  @item = Item.find(params[:id])

  # 出品者本人かどうかの分岐
  if @item.user_id != current_user.id
    redirect_to root_path
  end


記述したコードを1つずつ要約すると以下の通りです。

記述 説明
@item.user_id itemsテーブルにあるuser_idを取得。
itemモデルとuserモデルでアソシエーションを組んでいるため、このような記述ができます。
!= 「等しくない時」という意味。
current_user.id 【current_user】
devise機能を実装することで使えるメソッドです。
(作成するモデル名によってuserの部分が変わります。今回はモデル名にUserを使用しています)

ログインしているユーザーを取得してくれます。
.idをつけることで現在ログインしているユーザーのidを取得してくれます。
これを踏まえて、このコードを翻訳すると

「投稿(出品)したユーザーと現在のユーザーのidが違えばトップページに飛ばす。」

というふうになります。


実際にコントローラー内のeditだけに適応したければこんな感じになります。

items_controller.rb
# editアクションのみに対応
def edit
  @item = Item.find(params[:id])

  if @item.user_id != current_user.id
    redirect_to root_path
  end
end

けれど他にも、destroyアクション、updateアクションにも適応させたいです。
しかし、それぞれのアクション内に上記のコードを記述していくと非常に非常に冗長でかつ見にくいコードになってしまいます。
というか普通に邪魔くさいです。

items_controller.rb
# それぞれのアクションに記述
# かなり見にくいですよね

def edit
  if @item.user_id != current_user.id
    redirect_to root_path
  end
end

def update
  if @item.user_id != current_user.id
    redirect_to root_path
  end
end

def destroy
  if @item.user_id != current_user.id
    redirect_to root_path
  end
end


なので、複数同じコードを記述する場合は

コードを一箇所にまとめて、適応させたいアクションのみ指定してあげる

とした方がスッキリます!


ちなみに、editアクションを防いだ時点でupdateアクションは呼ばれないはずです。
しかし、検証ツールからボタンを表示させる、URLを直接入力をするといったことにより、updateアクションにたどり着いてしまう可能性があります!
なので、

  • editupdateアクション
  • newcreateアクション

はセットで記述した方がいいです。


コードを1つにまとめるとこんな感じになります。
before_actionで呼び出しているprevent_urlは私が勝手に考えた名前ですので、ここの命名は自由で大丈夫です!

items_controller.rb
class ItemsController < ApplicationController
  before_action :set_furima, only: [:edit, :update, :destroy]
  before_action :prevent_url, only: [:edit, :update, :destroy]

  def edit
  end

  def update
  end

  def destroy
  end


  private

    def set_furima
      @item = Item.find(params[:id])
    end

    def prevent_url
    if @item.user_id != current_user.id
      redirect_to root_path
    end
  end
  
end

これで、対ログアウトユーザーへの対策はできました!

4. 対 ログインしている自分

最後は、ログインしている自分自身にもURLを直打ちしてページ遷移させないようにする実装です。
個人的にここの実装がかなり手こずりました。

実装する内容としては、以下の2つです。

① ログイン状態の出品者が自身の出品した商品の購入画面に遷移できないようにする
② ログイン状態の出品者が売却済みの自身が出品した商品に対して、商品購入画面に遷移できないようにする

特に②の売却済みの商品という表現に苦戦しました。
結論から言うと、以下のコードを記述して①と②の対策をしました。

purchases_controller.rb
class PurchasesController < ApplicationController
   before_action :set_furima, only: [:index, :create]
   before_action :prevent_url, only: [:index, :create]

  def index
  end

  def create
  end

  private

    def set_furima
      @item = Item.find(params[:item_id])
    end

    def prevent_url
      if @item.user_id == current_user.id || @item.purchase != nil
        redirect_to root_path
      end
    end
  
end

上記のコードは論理演算子||(または)を用いて2つに分けています。


① ログイン状態の出品者が自身の出品した商品の購入画面に遷移できないようにする

@item.user_id == current_user.id

これは対 ログインユーザーで説明したコードとほぼ同じです。
内容としては、

「投稿(出品)したユーザーと現在のユーザーのidが同じであればトップページに飛ばす。」

となります。

② ログイン状態の出品者が売却済みの自身が出品した商品に対して、商品購入画面に遷移できないようにする

@item.purchase != nil


このコードを1つずつ要約すると以下の通りです。

記述 説明
@item.purchase itemsモデルに紐づくpurchasesモデルのitem_idを取得
!= 「等しくない時」という意味。
nil 「何もない、空」という意味。

つまり、②のコードを翻訳すると

「purchasesテーブルにあるitem_idがnil(空)でない時」

というふうになります。
purchasesテーブルは商品の購入情報を保存するテーブルであるため、

itemsテーブルのidとpurchasesテーブルのitem_idが一致する = 商品は購入されている

ということになります。
逆にいうと、itemsテーブルのidとpurchasesテーブルのitem_idが一致しなければ、その商品はまだ販売中ということになります。
スライド2 (5).JPG

また、売却済みの自身が出品した商品の編集画面にも遷移できないように実装したいので、itemsコントローラーのところにもコードを追加しました。

items_controller.rb
class ItemsController < ApplicationController
  before_action :set_furima, only: [:edit, :update, :destroy]
  before_action :prevent_url, only: [:edit, :update, :destroy]

  def edit
  end

  def update
  end

  def destroy
  end


  private

    def set_furima
      @item = Item.find(params[:id])
    end

    def prevent_url
    if @item.user_id != current_user.id || @item.purchase != nil # コードを追加
      redirect_to root_path
    end
  end
  
end

これで出品者本人に対するURL直打ち対策ができました!

5. まとめ

画面上のボタンの表示切り替えはしっかりできていても、URLの直打ち対策をうっかり忘れてしまう...なんてことがないようにしないといけませんね。
今回の実装でかなり勉強になりました!

11
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?