如何在Ruby on Rails中实现Active Storage私有附件的安全访问?
我正在用Ruby on Rails开发RESTful API,采用Active Storage管理附件,但Active Storage默认是公开分发附件,我希望实现私有分发,通过/assets/:name?token=TOKEN这样的路由来提供访问,前端调用方式如下:
// Front-end const flowerPhoto = new Image() const params = new URLSearchParams({ token }) flowerPhoto.src = `${API_URL}/assets/flower-photo?${params.toString()}`
查看Rails指南时,以下内容让我感到困惑:
所有Active Storage控制器默认公开可访问。生成的URL虽难以猜测,但设计上是永久有效的。如果你的文件需要更高等级的保护,请考虑实现认证控制器。
RedirectController会重定向到实际的服务端点。这种间接方式将服务URL与实际URL解耦,例如允许在不同服务中镜像附件以实现高可用性。重定向的HTTP过期时间为5分钟。
我尝试了以下实现:
路由配置
# config/routes.rb Rails.application.routes.draw do scope 'assets/:name', as: 'asset' do root 'assets#show', as: '' end end
模型定义
# app/models/asset.rb class Asset < ApplicationRecord has_one_attached :document end
控制器实现
# app/controllers/assets_controller.rb class AssetsController < ApplicationController include ActiveStorage::SetCurrent before_action :set_asset before_action :authorize def show redirect_to @asset.document.url end private def set_asset = @asset = Asset.find_by!(name: params[:name]) def authorize = user_authentication(params[:token]) end
关闭Active Storage默认路由
# config/application.rb module App class Application < Rails::Application # Public attachment URLs config.active_storage.draw_routes = false end end
但运行后遇到如下错误:
NoMethodError in AssetsController#show undefined method `rails_disk_service_url' for module redirect_to @asset.document.url
问题分析与解决方案
错误原因
关闭config.active_storage.draw_routes = false后,Active Storage的内置路由(包括生成签名URL所需的服务端逻辑)被禁用,导致url方法无法正常生成地址。另外,私有附件需要使用签名临时URL,而非永久公开URL,同时要确保存储服务配置为私有。
分步解决
1. 配置私有存储服务
修改config/storage.yml,将存储服务设置为私有(以本地Disk服务为例):
local: service: Disk root: <%= Rails.root.join("storage") %> public: false # 核心设置:标记存储为私有
如果使用S3等云存储,需对应设置私有权限,比如S3添加acl: private。
2. 恢复Active Storage内置路由
删除config/application.rb中的config.active_storage.draw_routes = false,保留默认的路由生成,因为Active Storage需要这些路由来处理签名URL和重定向逻辑。
调整自定义路由为更简洁的形式:
# config/routes.rb Rails.application.routes.draw do get '/assets/:name', to: 'assets#show', as: 'asset' end
3. 修正控制器逻辑
使用签名临时URL来提供私有附件访问,有两种实现方式:
方式一:直接生成存储服务的签名URL
# app/controllers/assets_controller.rb class AssetsController < ApplicationController include ActiveStorage::SetCurrent before_action :set_asset before_action :authorize def show # 生成有效期5分钟的签名URL,可自定义expires_in参数(单位秒) redirect_to @asset.document.service_url(expires_in: 300), allow_other_host: true end private def set_asset @asset = Asset.find_by!(name: params[:name]) end def authorize # 替换为你的Token验证逻辑,示例如下 unless valid_token?(params[:token]) render json: { error: '未授权访问' }, status: :unauthorized end end # 自定义Token验证方法,根据业务需求实现 def valid_token?(token) # 示例:验证Token是否关联有效用户 # User.exists?(api_token: token) end end
方式二:通过Active Storage RedirectController重定向(推荐)
这种方式符合Rails设计,解耦存储服务与应用路由:
def show # 生成指向RedirectController的签名URL,自动处理过期与重定向 redirect_to rails_blob_path(@asset.document, disposition: 'inline'), allow_other_host: true end
4. 确保附件以私有方式附加(可选)
如果全局存储服务未设为私有,可在模型中明确指定私有服务:
# app/models/asset.rb class Asset < ApplicationRecord has_one_attached :document, service: :local # 指定使用私有存储服务 end
内容的提问来源于stack exchange,提问作者Chiara Ani

