first commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// authCookieName 保存已通过授权校验的凭证。值为授权码的 SHA-256 十六进制摘要,
|
||||
// 不落明文;HttpOnly 使前端 JS 无法读取,降低泄露面。
|
||||
const authCookieName = "blackbean_auth"
|
||||
|
||||
// authCookieValue 计算授权码的稳定凭证值(SHA-256 摘要)。
|
||||
func authCookieValue(code string) string {
|
||||
sum := sha256.Sum256([]byte(code))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// authRequired 是访问授权中间件:cfg.AuthCode 为空(未启用授权)时直接放行;
|
||||
// 否则要求请求携带与授权码匹配的 Cookie,不匹配则 401 并中止后续处理。
|
||||
func (s *Server) authRequired(c *gin.Context) {
|
||||
if s.cfg.AuthCode == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if cookie, err := c.Cookie(authCookieName); err == nil && cookie == authCookieValue(s.cfg.AuthCode) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
fail(c, http.StatusUnauthorized, "需要授权码才能访问,请先在首页输入授权码")
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
// authStatus 返回授权状态:required=是否启用了授权,authorized=当前请求是否已通过授权。
|
||||
// 该接口始终放行(不挂授权中间件),前端据此决定是否展示授权界面。
|
||||
func (s *Server) authStatus(c *gin.Context) {
|
||||
required := s.cfg.AuthCode != ""
|
||||
authorized := false
|
||||
if required {
|
||||
if cookie, err := c.Cookie(authCookieName); err == nil {
|
||||
authorized = cookie == authCookieValue(s.cfg.AuthCode)
|
||||
}
|
||||
}
|
||||
ok(c, gin.H{"required": required, "authorized": authorized})
|
||||
}
|
||||
|
||||
// authLogin 校验授权码并签发 Cookie。
|
||||
// 该接口始终放行(不挂授权中间件);未启用授权时返回 400。
|
||||
func (s *Server) authLogin(c *gin.Context) {
|
||||
if s.cfg.AuthCode == "" {
|
||||
fail(c, http.StatusBadRequest, "本实例未启用访问授权")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
fail(c, http.StatusBadRequest, "请求体格式错误")
|
||||
return
|
||||
}
|
||||
// 常量时间比较,避免通过响应时差探测授权码
|
||||
want := []byte(s.cfg.AuthCode)
|
||||
got := []byte(strings.TrimSpace(req.Code))
|
||||
if len(want) != len(got) || subtle.ConstantTimeCompare(want, got) != 1 {
|
||||
fail(c, http.StatusUnauthorized, "授权码错误")
|
||||
return
|
||||
}
|
||||
http.SetCookie(c.Writer, &http.Cookie{
|
||||
Name: authCookieName,
|
||||
Value: authCookieValue(s.cfg.AuthCode),
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
ok(c, gin.H{"authorized": true})
|
||||
}
|
||||
Reference in New Issue
Block a user