Flask中如何获取仅单个用户可访问的图片安全URL?
Flask实现单个用户专属的安全图片URL方案
要实现仅单个用户可访问图片、防止URL猜测,同时返回带图片URL的JSON,核心思路是不直接暴露/uploads的静态路径,通过Flask路由做权限校验,再返回图片文件。下面给你两种实用方案:
方案一:基于登录态的权限校验(适合用户登录后访问)
这个方案依赖Flask-Login管理用户登录状态,每次访问图片时验证当前用户是否是图片的所有者。
1. 基础配置与模型
首先准备数据库模型,关联用户和图片:
from flask import Flask, jsonify, send_from_directory, url_for from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, login_required, current_user import os app = Flask(__name__) app.config['SECRET_KEY'] = 'your-strong-secret-key' # 替换成实际密钥 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'uploads') # 图片存储路径 db = SQLAlchemy(app) # 初始化Flask-Login login_manager = LoginManager() login_manager.init_app(app) # 用户模型 class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) # 其他字段:密码哈希、邮箱等 # 图片模型(关联用户ID,文件名直接用图片ID.jpeg) class Photo(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) user = db.relationship('User', backref=db.backref('photos', lazy=True)) # Flask-Login加载用户的回调 @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id))
2. 图片访问路由
创建路由处理图片请求,先校验权限再返回文件:
@app.route('/photos/<int:photo_id>') @login_required def serve_photo(photo_id): # 查询图片,不存在则返回404 photo = Photo.query.get_or_404(photo_id) # 校验当前用户是否是图片所有者 if photo.user_id != current_user.id: return jsonify({'error': '无权访问该图片'}), 403 # 返回uploads文件夹里的对应图片 return send_from_directory(app.config['UPLOAD_FOLDER'], f"{photo_id}.jpeg")
3. 返回带图片URL的JSON
查询条目时,生成安全的图片URL并返回:
# 假设你有一个Item模型关联了Photo class Item(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(120), nullable=False) photo_id = db.Column(db.Integer, db.ForeignKey('photo.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) @app.route('/api/items/<int:item_id>') @login_required def get_item(item_id): item = Item.query.get_or_404(item_id) # 校验条目是否属于当前用户 if item.user_id != current_user.id: return jsonify({'error': '无权访问该条目'}), 403 # 生成图片的完整URL photo_url = url_for('serve_photo', photo_id=item.photo_id, _external=True) return jsonify({ 'name': item.name, 'url': photo_url })
方案二:签名式临时URL(适合无需登录的场景,比如临时分享)
如果需要给用户生成可以短期访问的图片链接(不用登录),可以用itsdangerous给图片ID和用户ID签名,生成带有效期的安全URL。
1. 初始化签名工具
在配置里添加:
from itsdangerous import URLSafeSerializer, SignatureExpired # 初始化URL签名器 signer = URLSafeSerializer(app.config['SECRET_KEY'])
2. 生成签名URL的函数
def generate_signed_photo_url(photo_id, user_id, expires_in=3600): # 把图片ID和用户ID打包签名,设置1小时有效期 token = signer.dumps({'photo_id': photo_id, 'user_id': user_id}) return url_for('serve_signed_photo', token=token, _external=True)
3. 签名图片访问路由
@app.route('/photos/signed/<token>') def serve_signed_photo(token): try: # 解析签名,校验有效期 data = signer.loads(token, max_age=3600) except SignatureExpired: return jsonify({'error': '链接已过期'}), 401 except: return jsonify({'error': '无效的链接'}), 400 photo_id = data['photo_id'] user_id = data['user_id'] photo = Photo.query.get_or_404(photo_id) # 校验图片是否属于指定用户 if photo.user_id != user_id: return jsonify({'error': '无权访问该图片'}), 403 return send_from_directory(app.config['UPLOAD_FOLDER'], f"{photo_id}.jpeg")
4. 返回签名URL的JSON
修改之前的get_item接口:
@app.route('/api/items/<int:item_id>') @login_required def get_item(item_id): item = Item.query.get_or_404(item_id) if item.user_id != current_user.id: return jsonify({'error': '无权访问该条目'}), 403 # 生成签名式图片URL photo_url = generate_signed_photo_url(item.photo_id, current_user.id) return jsonify({ 'name': item.name, 'url': photo_url })
关键注意事项
- 禁止直接暴露uploads目录:不要把uploads设置为Flask的静态文件夹,否则用户可以直接通过
/uploads/1.jpeg访问,绕过权限校验。 - 上传图片时规范命名:创建Photo记录后,用自增的ID作为文件名保存(比如
1.jpeg),确保数据库里的ID和文件名一一对应。 - 权限校验要严格:无论是登录态还是签名URL,都必须校验用户是否是图片的所有者,防止越权访问。
内容的提问来源于stack exchange,提问作者X XXX X
相关产品推荐
相关产品推荐

