如何为Go Handler服务的图片添加请求上下文 避免URL暴露敏感信息
解决方案
方案1:直接内嵌Base64 QR码(最推荐,实现最简单,彻底规避接口暴露问题)
你在渲染页面的qrcodePageHandler里已经可以拿到合法的loc对象,完全可以直接在这个Handler内生成QR码,转成Base64 Data URI直接传给模板,不需要单独暴露/qr.png/接口:
import "encoding/base64" func qrcodePageHandler(w http.ResponseWriter, r *http.Request) { loc, ok := r.Context().Value("loc").(authentification.Location) if !ok { http.Error(w, fmt.Sprintf("Error while creating qrcode-Page: Failed to load Location"), http.StatusBadRequest) return } // 直接在页面渲染阶段生成QR码 _, qrcode, err := qrCodes.QRCode(loc, fmt.Sprintf("%s%s", SrvUrl, "?token="), qrRecoveryLevel, 400) if err != nil { http.Error(w, fmt.Sprintf("Error while creating qrcode: %v", err), http.StatusInternalServerError) return } // 转Base64 Data URI qrBase64 := fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(qrcode)) err := executeTemplateFunc("qr", qrPage, w, qrPageContent{ QrBase64: qrBase64, // 替换原来的Loc字段 Timer: RefreshTimer, }) if err != nil { http.Error(w, fmt.Sprintf("Error while creating qrcode-Page: %v", err), http.StatusInternalServerError) return } }
对应HTML模板直接改成:
<img alt="Failed to load QR-Code" src="{{.QrBase64}}">
这个方案完全删除了独立的QR码接口,用户不可能拿到独立的图片地址,自然不存在绕过访问的风险,且不需要额外依赖缓存、签名逻辑,性能损耗极小。
方案2:临时令牌校验(适合需要独立QR码接口、QR码需频繁刷新的场景)
- 新增一个带过期时间的临时存储(比如内存
sync.Map或Redis),用于映射临时令牌和Location的关系 - 页面渲染阶段生成随机一次性令牌,将令牌和对应Location存入缓存,过期时间和页面刷新时间
RefreshTimer对齐即可 - 模板中img的src改为
/qr.png/{{.TempToken}},不再暴露明文Location - 修改
qrWrapper逻辑,从路径中提取临时令牌,去缓存查询对应的Location,查询失败直接返回403,查询成功再执行后续逻辑
import ( "sync" "time" "github.com/google/uuid" ) // 示例:全局临时缓存,自动清理过期令牌 var qrTokenCache = sync.Map{} func qrcodePageHandler(w http.ResponseWriter, r *http.Request) { loc, ok := r.Context().Value("loc").(authentification.Location) if !ok { http.Error(w, fmt.Sprintf("Error while creating qrcode-Page: Failed to load Location"), http.StatusBadRequest) return } // 生成随机临时令牌 tempToken := uuid.NewString() // 存入缓存,带过期时间 qrTokenCache.Store(tempToken, map[string]interface{}{ "loc": loc, "expire": time.Now().Add(RefreshTimer * time.Second), }) // 异步清理过期令牌(也可以单独开定时任务全局清理) go func(token string) { time.Sleep(RefreshTimer * time.Second) qrTokenCache.Delete(token) }(tempToken) err := executeTemplateFunc("qr", qrPage, w, qrPageContent{ TempToken: tempToken, Timer: RefreshTimer, }) if err != nil { http.Error(w, fmt.Sprintf("Error while creating qrcode-Page: %v", err), http.StatusInternalServerError) return } } // 修改qrWrapper逻辑 func qrWrapper(lf locationCheckerFunc, cf portCheckerFunc, handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := path.Base(r.URL.Path) // 查缓存 cacheVal, ok := qrTokenCache.Load(token) if !ok { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } cacheData := cacheVal.(map[string]interface{}) // 校验过期 if cacheData["expire"].(time.Time).Before(time.Now()) { qrTokenCache.Delete(token) http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } locStruct := cacheData["loc"].(authentification.Location) // 原有端口校验逻辑保留 if !cf(w, *r, QrPort, http.StatusForbidden) { return } ctx := context.WithValue(r.Context(), "loc", locStruct) handler(w, r.WithContext(ctx)) } }
该方案即便用户拿到图片地址,令牌也会很快过期或失效,无法重复使用。
辅助加固手段
可在qrWrapper中新增Referer校验,仅允许来自本站点的请求访问QR码接口,作为额外防护层:
referer := r.Header.Get("Referer") if !strings.HasPrefix(referer, SrvUrl) { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return }
注:Referer可被伪造,仅能作为辅助校验,不能作为唯一判断依据
内容的提问来源于stack exchange,提问作者Schesam
相关产品推荐
相关产品推荐

