You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails中Shrine下载端点的账户级认证配置问题

问题背景与需求

我在Rails项目中使用Shrine的download_endpoint插件,已经通过Devise实现了路由层面的认证,现在要进一步添加基于账户的权限校验,确保用户只能访问自己账户下的图片。

初始配置尝试

最初的路由是:

Rails.application.routes.draw do
  get "/img/*rest", to: "download#confirm_response"
end

URL格式为/img/xxx,我想改成/img/:account_id/xxx,于是更新了路由:

Rails.application.routes.draw do
  get "/img/:account_id/*rest", to: "download#confirm_response"
end

对应的控制器代码如下,但不确定set_rack_response方法需要怎么修改才能正确引入account_id(已加载rack_response插件):

class DownloadController < ApplicationController
  def confirm_response
    @account = Account.find(params[:account_id])
    set_rack_response @account.to_rack_response
  end

  private

  def set_rack_response((status, headers, body))
    self.status = status
    self.headers.merge!(headers)
    self.response_body = body
  end
end

补充场景:多资源类型支持

后来我更新了路由和控制器,现在项目里有两个带图片的模型(Photo和Logo),希望两者都能使用download_endpoint。但我发现自己漏掉了把:account_id加入download_url生成逻辑的步骤,目标URL格式是/img/:account_id/xxx,想知道最可行的配置方案。

当前更新后的相关代码:

模型代码

# Account.rb
class Account < ApplicationRecord
  has_many :photos
  has_many :logos
end

# Photo.rb
class Photo < ApplicationRecord
  belongs_to :account

  include PhotoUploader::Attachment(:photo_file)
end

# Logo.rb
class Logo < ApplicationRecord
  belongs_to :account

  include LogoUploader::Attachment(:logo_file)
end

控制器代码

# Updated download_controller.rb
class DownloadController < ApplicationController
  def confirm_response
    @account = Account.find(params[:account_id])
    @photo = @account.photos.find(params[:photo_id])

    set_rack_response @photo.to_rack_response
  end

  private

  def set_rack_response((status, headers, body))
    self.status = status
    self.headers.merge!(headers)
    self.response_body = body
  end
end

路由代码

# Updated config/routes.rb
Rails.application.routes.draw do
  get "/img/:account_id/:photo_id/*rest", to: "download#confirm_response"
end

Shrine初始化代码

# Updated config/initializers/shrine.rb
Shrine.plugin :download_endpoint, prefix: "img", host: [:cloudfront]
Shrine.plugin :rack_response

视图代码

# Updated views/photos/show.html.erb
<%= image_tag(@photo.photo_file.download_url) %>

可行配置方案

1. 自定义Shrine下载URL生成逻辑

要让download_url自动带上account_id,在各自的上传器中重写download_url方法,确保生成的URL包含账户ID:

# app/uploaders/photo_uploader.rb
class PhotoUploader < Shrine
  def download_url(attachment, **options)
    super(attachment, **options.merge(account_id: attachment.record.account_id))
  end
end

# app/uploaders/logo_uploader.rb
class LogoUploader < Shrine
  def download_url(attachment, **options)
    super(attachment, **options.merge(account_id: attachment.record.account_id))
  end
end

同时更新Shrine的download_endpoint配置,让它识别URL中的account_id参数:

# config/initializers/shrine.rb
Shrine.plugin :download_endpoint, 
  prefix: "img", 
  host: [:cloudfront],
  url_options: ->(attachment) { { account_id: attachment.record.account_id } }

2. 调整路由,兼容两种资源

为Photo和Logo分别配置路由,避免逻辑混淆:

# config/routes.rb
Rails.application.routes.draw do
  get "/img/:account_id/photos/:photo_id/*rest", to: "download#photo"
  get "/img/:account_id/logos/:logo_id/*rest", to: "download#logo"
end

3. 更新下载控制器,实现权限校验

修改控制器,分方法处理两种资源,同时加入账户权限校验:

class DownloadController < ApplicationController
  # Devise登录校验
  before_action :authenticate_user!
  # 校验当前用户是否有权访问该账户
  before_action :validate_account_access

  def photo
    photo = @account.photos.find(params[:photo_id])
    # 传递rest参数给to_rack_response,对应文件存储路径
    set_rack_response photo.photo_file.to_rack_response(params[:rest])
  end

  def logo
    logo = @account.logos.find(params[:logo_id])
    set_rack_response logo.logo_file.to_rack_response(params[:rest])
  end

  private

  def validate_account_access
    @account = Account.find(params[:account_id])
    # 替换为你的用户-账户关联校验逻辑,比如:
    # unless current_user.account_id == @account.id
    #   raise ActionController::RoutingError.new("Not Found")
    # end
  end

  def set_rack_response((status, headers, body))
    self.status = status
    self.headers.merge!(headers)
    self.response_body = body
  end
end

4. 视图中使用正确的下载URL

现在download_url会自动生成包含account_id的地址,直接调用即可:

# views/photos/show.html.erb
<%= image_tag(@photo.photo_file.download_url) %>

# views/logos/show.html.erb
<%= image_tag(@logo.logo_file.download_url) %>

关键说明

  • to_rack_response必须接收params[:rest]参数,该参数对应URL中*rest部分,包含文件的实际存储路径信息
  • 权限校验必须结合Devise的current_user,严格限制用户只能访问自己账户下的资源,防止越权
  • 如果需要更通用的处理方式,可以在Shrine基类上传器中统一重写download_url,减少重复代码

内容的提问来源于stack exchange,提问作者Kobius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 14:18:20