Help us understand the problem. What is going on with this article?

ActiveStorageのファイルにアクセスするときに自前の認証機構を経由させる

More than 1 year has passed since last update.

Rails 5.2 から導入されたActiveStorageは rails_blob_path を使うことで署名つきURLへのリダイレクトを返してくれます。

# in view

rails_blob_path(object.image)
# => "/rails/active_storage/blobs/<signed_id>/<filename>"
# 上記のURLにアクセスするとサービスに応じた認証URLへリダイレクトしてくれる。
# たとえばGCSなら `https://storage.googleapis.com/<bucket-name>/xxxyyyzzz`
# というようなURLにリダイレクトされる。

認証URLには有効期限がついている(デフォルトで5分)ので、/rails/active_storage/blobs/** にアクセスが来たときに適当に認証処理を挟むとファイルに対して簡易的なアクセス制御が実現できます。これを実現するには上記のエンドポイントを処理する ActiveStorage::BlobsController を上書きすれば良いです。参考のために ActiveStorage::BlobsControllerのデフォルトの実装を見てみましょう。

rails/blobs_controller.rb at bc9fb9cf8b5dbe8ecf399ffd5d48d84bdb96a9db · rails/rails

# frozen_string_literal: true

# Take a signed permanent reference for a blob and turn it into an expiring service URL for download.
# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
# security-through-obscurity factor of the signed blob references, you'll need to implement your own
# authenticated redirection controller.
class ActiveStorage::BlobsController < ActiveStorage::BaseController
  include ActiveStorage::SetBlob

  def show
    expires_in ActiveStorage::Blob.service.url_expires_in
    redirect_to @blob.service_url(disposition: params[:disposition])
  end
end

実はここのコメントにも「アクセス制御をしたい場合は自前でリダイレクトのためのコントローラーを実装しろ」と書いてあります。

というわけでapp/controllers/active_storage/blobs_controller.rbファイルを作成し、アクセス制御のコードを挿入します。アクセス制御の内容はサービスによって様々でしょうから適宜書き換えてみてください。

app/controllers/active_storage/blobs_controller.rb
class ActiveStorage::BlobsController < ActiveStorage::BaseController
  include ActiveStorage::SetBlob

  def show
    expires_in ActiveStorage::Blob.service.url_expires_in

    if access_allowed?(@blob)
      redirect_to @blob.service_url(disposition: params[:disposition])
    else
      head :forbidden
    end
  end

  private
  def access_allowed?(blob)
    # サービスに応じたアクセス処理を記述する。

    ...
  end
end

どのレコードにアクセスが来たのか

上記のコントローラーで参照できるインスタンスは基本的に@blobだけで、Blobオブジェクトはサービス固有の情報を一切持っていないのでこのままではアクセス制御は行えません。しかしながら、@blobに一対多の関係で紐づいている@blob.attachmentsが持つrecordという参照を利用することで、元のレコードを(だいたい)特定できます。Attachmentが複数なので結局どのレコードに対するアクセスなのかは判明しませんが、基本的にはそのうちのどれか一つに対するアクセスが許可されるならば、ここでは認証URLを返して良いと思います(つまり blob.attachments.any? { ... }true ならOK)。

単一のファイル(Blob)を複数のレコードに対して紐づけることも想定されているためこのような実装になっているそうです( @scivola さんありがとうございます!)。そのような場合は単一のBlobに対し複数のレコードと、「関係」を抽象化したAttachmentが存在するわけですが、複数のレコードのうちどれかに閲覧権を持つユーザーならファイル自体の閲覧権も持つでしょう。

ActiveStorage::BlobsController ではどのレコードに対するアクセスなのか特定できないためこのような回りくどいことをしているわけですが、もしかしたらレコードを特定する方法があるかもしれません(し、ないなら導入しても良いんじゃないでしょうか?)。

expires_in だけ変えたい

認証URLの有効期限(expires_in)はコントローラーで直接変えてもいいですが、config/application.rb 等で ActiveStorage::Service.url_expires_in を設定することでも変更できます。

config/application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module HogeApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    ActiveStorage::Service.url_expires_in = 1.minute
  end
end
karszawa
Super Ultra Hyper Miracle Romantic Web Engineer
https://karszawa.dev
mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした