[GH-ISSUE #4102] 配置静态文件服务时如果使用了basicauth,手机浏览器下载文件时并没有发送Authorization header导致下载文件失败 #3235

Closed
opened 2026-05-05 14:05:22 -06:00 by gitea-mirror · 4 comments
Owner

Originally created by @hu198021688500 on GitHub (Mar 24, 2024).
Original GitHub issue: https://github.com/fatedier/frp/issues/4102

Bug Description

配置静态文件服务时如果使用了basicauth,手机浏览器下载文件时并没有发送Authorization header导致下载文件失败

frpc Version

0.54.0

frps Version

0.52.3

System Architecture

server linux/amd64 client windows/amd64

Configurations

serverAddr = "47.11.12.2"
serverPort = 7000

log.level = "trace"
log.maxDays = 3

proxies
name = "image_static_file"
type = "tcp"
remotePort = 2239

transport.useCompression = true
transport.bandwidthLimit = "1MB"
transport.bandwidthLimitMode = "client"

[proxies.plugin]
type = "static_file"
localPath = "G:/"
stripPrefix = ""
httpUser = "user"
httpPassword = "123"

Logs

No response

Steps to reproduce

...

Affected area

  • Docs
  • Installation
  • Performance and Scalability
  • Security
  • User Experience
  • Test and Release
  • Developer Infrastructure
  • Client Plugin
  • Server Plugin
  • Extensions
  • Others
Originally created by @hu198021688500 on GitHub (Mar 24, 2024). Original GitHub issue: https://github.com/fatedier/frp/issues/4102 ### Bug Description 配置静态文件服务时如果使用了basicauth,手机浏览器下载文件时并没有发送Authorization header导致下载文件失败 ### frpc Version 0.54.0 ### frps Version 0.52.3 ### System Architecture server linux/amd64 client windows/amd64 ### Configurations serverAddr = "47.11.12.2" serverPort = 7000 log.level = "trace" log.maxDays = 3 [[proxies]] name = "image_static_file" type = "tcp" remotePort = 2239 transport.useCompression = true transport.bandwidthLimit = "1MB" transport.bandwidthLimitMode = "client" [proxies.plugin] type = "static_file" localPath = "G:/" stripPrefix = "" httpUser = "user" httpPassword = "123" ### Logs _No response_ ### Steps to reproduce 1. 2. 3. ... ### Affected area - [ ] Docs - [ ] Installation - [ ] Performance and Scalability - [ ] Security - [ ] User Experience - [ ] Test and Release - [ ] Developer Infrastructure - [ ] Client Plugin - [ ] Server Plugin - [ ] Extensions - [ ] Others
Author
Owner

@hu198021688500 commented on GitHub (Mar 24, 2024):

修改文件
frp\pkg\util\net\http.go

type HTTPAuthMiddleware struct {
user string
passwd string
authFailDelay time.Duration

expires  time.Duration
sessions map[string]time.Time

}

func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware {
middleware := &HTTPAuthMiddleware{
user: user,
passwd: passwd,

	expires:  120 * time.Second,
	sessions: make(map[string]time.Time),
}
middleware.cleanSession()
return middleware

}

func (authMid *HTTPAuthMiddleware) signIn(w http.ResponseWriter, r *http.Request) bool {
reqUser, reqPasswd, hasAuth := r.BasicAuth()
if (authMid.user == "" && authMid.passwd == "") ||
(hasAuth && util.ConstantTimeEqString(reqUser, authMid.user) &&
util.ConstantTimeEqString(reqPasswd, authMid.passwd)) {
sessionToken := uuid.NewString()
expiresAt := time.Now().Add(authMid.expires)

	authMid.sessions[sessionToken] = expiresAt
	http.SetCookie(w, &http.Cookie{
		Name:    "session_token",
		Value:   sessionToken,
		Expires: expiresAt,
	})
	log.Debugf("signIn success and set cookie %s", sessionToken)
	return true
} else {
	log.Debugf("signIn fail")
	return false
}

}

func (authMid *HTTPAuthMiddleware) auth(r *http.Request) bool {
c, err := r.Cookie("session_token")
if err != nil {
log.Debugf("get cookie error: %v", err)
return false
}
_, exists := authMid.sessions[c.Value]
if exists {
log.Debugf("exist session %s and refresh it", c.Value)
authMid.sessions[c.Value] = time.Now().Add(authMid.expires)
}
return exists
}

func (authMid *HTTPAuthMiddleware) cleanSession() {
ticker := time.NewTicker(authMid.expires)
go func() {
for {
<-ticker.C
log.Debugf("start clean session...")
for k, v := range authMid.sessions {
if v.Before(time.Now()) {
log.Debugf("delete session %s", k)
delete(authMid.sessions, k)
}
}
}
}()
}

func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if authMid.auth(r) {
next.ServeHTTP(w, r)
} else if authMid.signIn(w, r) {
next.ServeHTTP(w, r)
} else {
if authMid.authFailDelay > 0 {
time.Sleep(authMid.authFailDelay)
}
w.Header().Set("WWW-Authenticate", Basic realm="Restricted")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
})
}

<!-- gh-comment-id:2016812861 --> @hu198021688500 commented on GitHub (Mar 24, 2024): 修改文件 frp\pkg\util\net\http.go type HTTPAuthMiddleware struct { user string passwd string authFailDelay time.Duration expires time.Duration sessions map[string]time.Time } func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware { middleware := &HTTPAuthMiddleware{ user: user, passwd: passwd, expires: 120 * time.Second, sessions: make(map[string]time.Time), } middleware.cleanSession() return middleware } func (authMid *HTTPAuthMiddleware) signIn(w http.ResponseWriter, r *http.Request) bool { reqUser, reqPasswd, hasAuth := r.BasicAuth() if (authMid.user == "" && authMid.passwd == "") || (hasAuth && util.ConstantTimeEqString(reqUser, authMid.user) && util.ConstantTimeEqString(reqPasswd, authMid.passwd)) { sessionToken := uuid.NewString() expiresAt := time.Now().Add(authMid.expires) authMid.sessions[sessionToken] = expiresAt http.SetCookie(w, &http.Cookie{ Name: "session_token", Value: sessionToken, Expires: expiresAt, }) log.Debugf("signIn success and set cookie %s", sessionToken) return true } else { log.Debugf("signIn fail") return false } } func (authMid *HTTPAuthMiddleware) auth(r *http.Request) bool { c, err := r.Cookie("session_token") if err != nil { log.Debugf("get cookie error: %v", err) return false } _, exists := authMid.sessions[c.Value] if exists { log.Debugf("exist session %s and refresh it", c.Value) authMid.sessions[c.Value] = time.Now().Add(authMid.expires) } return exists } func (authMid *HTTPAuthMiddleware) cleanSession() { ticker := time.NewTicker(authMid.expires) go func() { for { <-ticker.C log.Debugf("start clean session...") for k, v := range authMid.sessions { if v.Before(time.Now()) { log.Debugf("delete session %s", k) delete(authMid.sessions, k) } } } }() } func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if authMid.auth(r) { next.ServeHTTP(w, r) } else if authMid.signIn(w, r) { next.ServeHTTP(w, r) } else { if authMid.authFailDelay > 0 { time.Sleep(authMid.authFailDelay) } w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) } }) }
Author
Owner

@fatedier commented on GitHub (Mar 28, 2024):

没有理解,发送 Authorization header 不就可以了

<!-- gh-comment-id:2024329827 --> @fatedier commented on GitHub (Mar 28, 2024): 没有理解,发送 Authorization header 不就可以了
Author
Owner

@hu198021688500 commented on GitHub (May 15, 2024):

没有理解,发送 Authorization header 不就可以了

使用华为p40 自带的华为浏览器,在下载文件时,浏览器不会自动发送Authorization header,有cookie,因此静态服务其接收不到认证信息,导致下载失败。 不可能自己去修改浏览器base auth的默认行为呀
正常浏览文件列表时有发送auth header
微信图片_20240515155251
点击文件下载时没有收到auth header
微信图片_20240515154822
微信图片_20240515154843

<!-- gh-comment-id:2111814774 --> @hu198021688500 commented on GitHub (May 15, 2024): > 没有理解,发送 Authorization header 不就可以了 使用华为p40 自带的华为浏览器,在下载文件时,浏览器不会自动发送Authorization header,有cookie,因此静态服务其接收不到认证信息,导致下载失败。 不可能自己去修改浏览器base auth的默认行为呀 正常浏览文件列表时有发送auth header <img width="401" alt="微信图片_20240515155251" src="https://github.com/fatedier/frp/assets/1234970/37ab19ed-dae0-4be6-9613-cb3ff0a02de5"> 点击文件下载时没有收到auth header <img width="407" alt="微信图片_20240515154822" src="https://github.com/fatedier/frp/assets/1234970/b6eb8258-e5e4-4fca-aca0-83c040d92afc"> <img width="293" alt="微信图片_20240515154843" src="https://github.com/fatedier/frp/assets/1234970/d7c633a0-f2a1-4395-85e7-1b5dc2b08612">
Author
Owner

@fatedier commented on GitHub (May 15, 2024):

那尝试换一个其他的浏览器?

这个问题和此项目无关,也可以给华为浏览器提交意见反馈,从根本上解决问题。

<!-- gh-comment-id:2111823632 --> @fatedier commented on GitHub (May 15, 2024): 那尝试换一个其他的浏览器? 这个问题和此项目无关,也可以给华为浏览器提交意见反馈,从根本上解决问题。
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/frp#3235
No description provided.