You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go Web应用集成Keycloak OIDC遇重定向循环问题求助

问题:Keycloak OIDC认证中间件导致重定向循环

我正在开发大学项目的后端服务器,整体已基本完成。朋友帮我搭建了Keycloak实例,我已为服务器配置好客户端,Keycloak的RedirectURI设置为http://localhost:8080/callback。

我希望保护所有路由,要求用户必须登录才能访问,但尝试用中间件实现认证时,始终陷入重定向循环。ChatGPT给出的方案完全没用,特此求助。

原Router函数

func Network_Router() { 
        r := mux.NewRouter() r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("public/assets"))))
        r.PathPrefix("/evaluation/data/").Handler(http.StripPrefix("/evaluation/data/", http.FileServer(http.Dir("../hugo/this/data"))))
    
        // Add Routing
        r.HandleFunc("/", Network_Homepage_Handler)
        r.HandleFunc("/createEvaluation", Network_CreateEvaluation_Handler)
        r.HandleFunc("/evaluation/{subdomain}", Network_EvaluationSite_Handler)
        r.HandleFunc("/api/evaldata", API_returnData)
    
        r.HandleFunc("/callback", CallbackHandler)
    
        http.ListenAndServe(":8080", r)
    }

导致重定向循环的完整代码

package main

import (
"log"
"net/http"
"context"
"fmt"

    "github.com/coreos/go-oidc"
    "github.com/gorilla/mux"
    _ "github.com/mattn/go-sqlite3"
    "golang.org/x/oauth2"
    "github.com/gorilla/sessions"

)

var (
clientID     = "data"
clientSecret = "data"
redirectURI  = "http://localhost:8080/callback"
keycloakIssuer = "data"

    store = sessions.NewCookieStore([]byte("test"))

) // 实际使用的是正确的配置数据

func main() {
Network_Router()
}

func Network_Router() {
r := mux.NewRouter()

    r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("public/assets"))))
    r.PathPrefix("/evaluation/data/").Handler(http.StripPrefix("/evaluation/data/", http.FileServer(http.Dir("../hugo/this/data"))))
    
    // Add Routing
    r.HandleFunc("/", AuthMiddleware(Network_Homepage_Handler))
    r.HandleFunc("/createEvaluation", AuthMiddleware(Network_CreateEvaluation_Handler))
    r.HandleFunc("/evaluation/{subdomain}", AuthMiddleware(Network_EvaluationSite_Handler))
    r.HandleFunc("/api/evaldata", AuthMiddleware(API_returnData))
    r.HandleFunc("/login", KeycloakLoginHandler)
    
    r.HandleFunc("/callback", CallbackHandler)
    
    http.ListenAndServe(":8080", r)

}

func CallbackHandler(w http.ResponseWriter, r *http.Request) {
// Retrieve the session
session, err := store.Get(r, "session")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

    // Get the original requested URL from the session or query parameter
    originalURL, ok := session.Values["originalURL"].(string)
    if !ok {
        originalURL = "/"
    }
    
    // Clear the session value
    delete(session.Values, "originalURL")
    session.Save(r, w)
    
    state := r.URL.Query().Get("state")
    code := r.URL.Query().Get("code")
    
    log.Printf("CallbackHandler: state=%s, code=%s", state, code)
    
    // Perform token exchange and other necessary validation here
    
    // Redirect to the original requested location after successful authentication
    http.Redirect(w, r, originalURL, http.StatusSeeOther)

}

func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Retrieve the session
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

        idToken, err := validateToken(r)
        if err != nil {
            // User is not authenticated
            log.Printf("User not authenticated: %v", err)
    
            // Store the original requested URL in the session
            session.Values["originalURL"] = r.URL.Path
            session.Save(r, w)
    
            http.Redirect(w, r, "/login", http.StatusSeeOther)
            return
        }
    
        // User is authenticated, print some information for debugging
        log.Printf("Debug bug bug, %v", idToken)
    
        // Proceed to the next handler
        next.ServeHTTP(w, r)
    }

}

func validateToken(r *http.Request) (*oidc.IDToken, error) {
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, keycloakIssuer)
if err != nil {
return nil, err
}

    config := oauth2.Config{
        ClientID:     clientID,
        ClientSecret: clientSecret,
        RedirectURL:  redirectURI,
        Endpoint:     provider.Endpoint(),
        Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
    }
    
    // Exchange the authorization code for a token
    token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
    if err != nil {
        return nil, err
    }
    
    // Verify the ID token
    rawIDToken, ok := token.Extra("id_token").(string)
    if !ok {
        return nil, fmt.Errorf("missing id_token in OAuth2 token response")
    }
    
    idToken, err := provider.Verifier(&oidc.Config{ClientID: clientID}).Verify(ctx, rawIDToken)
    if err != nil {
        return nil, err
    }
    
    log.Printf("validateToken: code=%s, clientID=%s, clientSecret=%s", r.URL.Query().Get("code"), clientID, clientSecret)
    
    return idToken, nil

}

func KeycloakLoginHandler(w http.ResponseWriter, r *http.Request) {
// Redirect the user to the Keycloak login page
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, keycloakIssuer)
if err != nil {
log.Printf("Error creating OIDC provider: %v", err)
http.Error(w, "Unable to create OIDC provider", http.StatusInternalServerError)
return
}

    config := oauth2.Config{
        ClientID:     clientID,
        ClientSecret: clientSecret,
        RedirectURL:  redirectURI,
        Endpoint:     provider.Endpoint(),
        Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
    }
    
    state, err := randomString(10)
    if err != nil {
        log.Printf("Sucks to be you")
    }
    authCodeURL := config.AuthCodeURL(state)
    
    log.Printf("Redirecting to Keycloak login page: %s", authCodeURL)
    http.Redirect(w, r, authCodeURL, http.StatusSeeOther)

}

注:这段代码大部分由ChatGPT生成,效果很差。除OIDC相关方法外,其他所有使用的方法均能正常工作。

内容的提问来源于stack exchange,提问作者panderu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 20:35:56