对接
This commit is contained in:
@@ -43,6 +43,33 @@ func AuthenticateWebSocket(c *gin.Context, auth TokenAuthenticator) bool {
|
||||
return authenticate(c, auth, true)
|
||||
}
|
||||
|
||||
// AuthenticateWebSocketOptional enriches a public WebSocket connection when
|
||||
// a valid token is available. Missing, expired, malformed, or revoked tokens
|
||||
// fall back to an anonymous session; invalid cookie state is cleared so a
|
||||
// stale login cannot prevent public live viewing.
|
||||
func AuthenticateWebSocketOptional(c *gin.Context, auth TokenAuthenticator) bool {
|
||||
token := RequestToken(c, true)
|
||||
if token == "" || auth == nil {
|
||||
return false
|
||||
}
|
||||
value, err, _ := refreshTokens.Do(token, func() (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.WithoutCancel(c.Request.Context()), tokenAuthTimeout)
|
||||
defer cancel()
|
||||
return auth.AuthenticateToken(ctx, token)
|
||||
})
|
||||
if err != nil {
|
||||
httpx.SetCookie(c, "x-token", "", -1)
|
||||
return false
|
||||
}
|
||||
authentication, ok := value.(*system.TokenAuthentication)
|
||||
if !ok || authentication == nil || authentication.Claims == nil {
|
||||
httpx.SetCookie(c, "x-token", "", -1)
|
||||
return false
|
||||
}
|
||||
applyTokenAuthentication(c, authentication)
|
||||
return true
|
||||
}
|
||||
|
||||
func authenticate(c *gin.Context, auth TokenAuthenticator, allowQueryToken bool) bool {
|
||||
token := RequestToken(c, allowQueryToken)
|
||||
if token == "" {
|
||||
@@ -74,13 +101,17 @@ func authenticate(c *gin.Context, auth TokenAuthenticator, allowQueryToken bool)
|
||||
httpx.NoAuth(c, "无法处理此token")
|
||||
return false
|
||||
}
|
||||
applyTokenAuthentication(c, authentication)
|
||||
return true
|
||||
}
|
||||
|
||||
func applyTokenAuthentication(c *gin.Context, authentication *system.TokenAuthentication) {
|
||||
if authentication.Refreshed != nil {
|
||||
c.Header("new-token", authentication.Refreshed.Value)
|
||||
c.Header("new-expires-at", strconv.FormatInt(authentication.Refreshed.ExpiresAt.Unix(), 10))
|
||||
httpx.SetCookie(c, "x-token", authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds()))
|
||||
}
|
||||
c.Set(claimsKey, authentication.Claims)
|
||||
return true
|
||||
}
|
||||
|
||||
// RequestToken extracts the authentication token accepted by HTTP handlers.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"kra/internal/biz/system"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -79,3 +80,36 @@ func TestAuthenticateWebSocketSupportsBearerHeaderAndReportsInvalidToken(t *test
|
||||
t.Fatalf("invalid token status=%d, want %d", invalidRecorder.Code, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticateWebSocketOptionalFallsBackToAnonymous(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
stub := &tokenAuthenticatorStub{err: system.ErrTokenExpired}
|
||||
recorder := httptest.NewRecorder()
|
||||
context, _ := gin.CreateTestContext(recorder)
|
||||
context.Request = httptest.NewRequest(http.MethodGet, "/ws", nil)
|
||||
context.Request.AddCookie(&http.Cookie{Name: "x-token", Value: "expired-token"})
|
||||
|
||||
if AuthenticateWebSocketOptional(context, stub) {
|
||||
t.Fatal("expired token was treated as authenticated")
|
||||
}
|
||||
if Claims(context) != nil {
|
||||
t.Fatalf("anonymous fallback retained claims: %#v", Claims(context))
|
||||
}
|
||||
if recorder.Code == http.StatusUnauthorized {
|
||||
t.Fatalf("optional authentication interrupted public handshake: body=%s", recorder.Body.String())
|
||||
}
|
||||
if cookie := recorder.Header().Get("Set-Cookie"); !strings.Contains(cookie, "x-token=") || !strings.Contains(cookie, "Max-Age=0") {
|
||||
t.Fatalf("expired token cookie was not cleared: %q", cookie)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticateWebSocketOptionalStoresValidClaims(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
stub := &tokenAuthenticatorStub{claims: &system.AuthClaims{ID: 9}}
|
||||
context, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
context.Request = httptest.NewRequest(http.MethodGet, "/ws?token=valid-token", nil)
|
||||
|
||||
if !AuthenticateWebSocketOptional(context, stub) || Claims(context) == nil || Claims(context).ID != 9 {
|
||||
t.Fatalf("valid optional token was not applied: claims=%#v", Claims(context))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user