34
21

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 5 years have passed since last update.

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

Last updated at Posted at 2018-08-16

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
34
21
2

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
34
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?