File size: 1,690 Bytes
6d60378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Package httpauth 网关与面板共用的 Bearer 鉴权原语。
//
// 单独成包的原因:server(/v1/*、/status)与 panel(/panel/api/*)两处鉴权
// 必须完全同口径——此前各自复制了一份"字符串直接比较"的实现,既容易漂移,
// 又都带计时侧信道。统一到这里后,口径只有一份,且天然常量时间比较。
package httpauth

import (
	"crypto/sha256"
	"crypto/subtle"
	"net/http"
	"strings"
)

// bearerPrefix 认证方案前缀(大小写敏感,与 HTTP 规范及既有实现一致)。
const bearerPrefix = "Bearer "

// VerifyBearer 校验请求头是否携带正确的 Bearer 密钥。
//
// key 为空表示"未启用鉴权",恒返回 true(调用方据此放行)。
// 比较用 SHA-256 摘要 + subtle.ConstantTimeCompare:
//   - 常量时间,不因前缀匹配长度而泄露信息;
//   - 先摘要再比较,长度差异被吸收进摘要(不会因长度不同提前返回);
//   - 摘要本身不可逆,即便有侧信道也拿不到密钥原文。
func VerifyBearer(r *http.Request, key string) bool {
	if key == "" {
		return true
	}
	authz := r.Header.Get("Authorization")
	if !strings.HasPrefix(authz, bearerPrefix) {
		// 缺头/方案不对:仍走一次摘要比较,保持耗时形状一致。
		subtle.ConstantTimeCompare(digest(""), digest(key))
		return false
	}
	tok := authz[len(bearerPrefix):]
	return subtle.ConstantTimeCompare(digest(tok), digest(key)) == 1
}

// digest 返回 s 的 SHA-256(定长 32 字节,供常量时间比较)。
func digest(s string) []byte {
	sum := sha256.Sum256([]byte(s))
	return sum[:]
}