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
相关产品推荐
相关产品推荐

