fix(apiserver): match JWT-excluded paths exactly, wildcard only for /* (bug-1962)
Cause: isExcludedPath (middleware/validatehandler.go) dropped the last character of every excluded entry and prefix-matched, so /ping excused any path starting with /pin (e.g. /pinX) from JWT validation, while /swagger/*any (trimmed to /swagger/*an) never matched. Fix: entries match exactly; an entry whose last segment is a gin catch-all (/swagger/*any) matches any path under the part before the "*". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d720132c8c
commit
387a421c57
2 changed files with 76 additions and 14 deletions
|
|
@ -44,23 +44,30 @@ func ValidateHandler() gin.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func isExcludedPath(path string) bool {
|
||||
// jwtExcludedPaths are the routes that skip JWT validation. An entry is
|
||||
// matched exactly, unless its last segment is a gin catch-all ("*name", as in
|
||||
// the route "/swagger/*any"): then any path under the part before the "*"
|
||||
// matches (bug-1962).
|
||||
var jwtExcludedPaths = []string{
|
||||
"/ping",
|
||||
"/xce/v1/auth/logon",
|
||||
"/swagger/*any",
|
||||
"/xce/v1/auth/logout",
|
||||
}
|
||||
|
||||
allowed := false
|
||||
// define some excluded paths
|
||||
excludedPaths := []string{
|
||||
"/ping",
|
||||
"/xce/v1/auth/logon",
|
||||
"/swagger/*any",
|
||||
"/xce/v1/auth/logout",
|
||||
}
|
||||
for _, excludedPath := range excludedPaths {
|
||||
if strings.HasPrefix(path, excludedPath[:len(excludedPath)-1]) { // 去掉最后的 *
|
||||
allowed = true
|
||||
break
|
||||
func isExcludedPath(path string) bool {
|
||||
for _, excludedPath := range jwtExcludedPaths {
|
||||
if i := strings.LastIndex(excludedPath, "/*"); i >= 0 {
|
||||
if strings.HasPrefix(path, excludedPath[:i+1]) {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if path == excludedPath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return allowed
|
||||
return false
|
||||
}
|
||||
|
||||
// Token validation middleware
|
||||
|
|
|
|||
|
|
@ -150,3 +150,58 @@ func TestIsValidToken_PinsSigningAlgorithm(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// bug-1962: exact excluded entries match only themselves; only the wildcard
|
||||
// entry (/swagger/*any) matches by prefix.
|
||||
func TestIsExcludedPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
want bool
|
||||
}{
|
||||
{"/ping", true},
|
||||
{"/xce/v1/auth/logon", true},
|
||||
{"/xce/v1/auth/logout", true},
|
||||
{"/swagger/index.html", true},
|
||||
{"/swagger/doc.json", true},
|
||||
// prefix look-alikes must not skip JWT validation
|
||||
{"/pinX", false},
|
||||
{"/pin", false},
|
||||
{"/ping/x", false},
|
||||
{"/pingpong", false},
|
||||
{"/xce/v1/auth/logonX", false},
|
||||
{"/xce/v1/auth/logo", false},
|
||||
{"/xce/v1/auth/logout/x", false},
|
||||
{"/swaggerX", false},
|
||||
{"/xce/v1/jobs", false},
|
||||
{"/", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := isExcludedPath(tt.path); got != tt.want {
|
||||
t.Errorf("isExcludedPath(%q) = %v, want %v", tt.path, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bug-1962: a request to a path that only shares a prefix with an excluded
|
||||
// entry must still be rejected without a token; the excluded entry itself
|
||||
// still needs none.
|
||||
func TestValidateHandler_PrefixLookAlikeNeedsToken(t *testing.T) {
|
||||
setTestAuthConfig(t)
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(ValidateHandler())
|
||||
r.GET("/pinX", func(ctx *gin.Context) { ctx.String(http.StatusOK, "reached") })
|
||||
r.GET("/ping", func(ctx *gin.Context) { ctx.String(http.StatusOK, "pong") })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/pinX", nil))
|
||||
if strings.Contains(w.Body.String(), "reached") {
|
||||
t.Errorf("/pinX without a token reached the handler (status %d)", w.Code)
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/ping", nil))
|
||||
if w.Code != http.StatusOK || w.Body.String() != "pong" {
|
||||
t.Errorf("/ping without a token: status %d body %q, want 200 pong", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue