使用Keycloak保护Golang REST API的安全

使用Keycloak保护Golang REST API的安全 我正在使用Golang开发REST API服务器,并希望通过Keycloak来保护所有API。有人能建议如何在Golang API服务器上实现单点登录吗?

3 回复

这个方法对我很有效……非常感谢……

更多关于使用Keycloak保护Golang REST API的安全的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中集成Keycloak来保护REST API并实现单点登录(SSO),可以使用gocloak库。以下是一个完整的实现示例:

首先安装依赖:

go get github.com/Nerzal/gocloak/v13

1. 配置Keycloak客户端 在Keycloak管理控制台创建一个客户端,设置:

  • 访问类型:confidential
  • 有效重定向URI:你的API回调地址
  • 启用客户端认证

2. Golang中间件实现

package main

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

    "github.com/Nerzal/gocloak/v13"
    "github.com/gin-gonic/gin"
)

type KeycloakConfig struct {
    Realm        string
    ClientID     string
    ClientSecret string
    Hostname     string
}

var (
    keycloakClient gocloak.GoCloak
    config         = KeycloakConfig{
        Realm:        "your-realm",
        ClientID:     "your-client-id",
        ClientSecret: "your-client-secret",
        Hostname:     "http://localhost:8080",
    }
)

func init() {
    keycloakClient = gocloak.NewClient("http://localhost:8080/auth")
}

// Keycloak中间件 - 验证访问令牌
func KeycloakMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
            c.Abort()
            return
        }

        token := strings.TrimPrefix(authHeader, "Bearer ")
        if token == authHeader {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Bearer token required"})
            c.Abort()
            return
        }

        // 验证令牌
        ctx := context.Background()
        _, err := keycloakClient.IntrospectToken(ctx, token, config.ClientID, config.ClientSecret, config.Realm)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }

        c.Next()
    }
}

// 获取用户信息
func getUserInfo(c *gin.Context) {
    authHeader := c.GetHeader("Authorization")
    token := strings.TrimPrefix(authHeader, "Bearer ")
    
    ctx := context.Background()
    userInfo, err := keycloakClient.GetUserInfo(ctx, token, config.Realm)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "sub":               userInfo.Sub,
        "email":             userInfo.Email,
        "preferred_username": userInfo.PreferredUsername,
    })
}

// 登录处理
func loginHandler(c *gin.Context) {
    ctx := context.Background()
    
    token, err := keycloakClient.Login(ctx, config.ClientID, config.ClientSecret, config.Realm, "username", "password")
    if err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Login failed"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "access_token":  token.AccessToken,
        "refresh_token": token.RefreshToken,
        "expires_in":    token.ExpiresIn,
    })
}

func main() {
    r := gin.Default()

    // 公开路由
    r.POST("/login", loginHandler)

    // 受保护的路由组
    protected := r.Group("/api")
    protected.Use(KeycloakMiddleware())
    {
        protected.GET("/userinfo", getUserInfo)
        protected.GET("/protected", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"message": "This is a protected endpoint"})
        })
    }

    log.Fatal(r.Run(":8081"))
}

3. 使用示例

获取访问令牌:

curl -X POST http://localhost:8081/login \
  -H "Content-Type: application/json" \
  -d '{"username":"your-username","password":"your-password"}'

访问受保护的API:

curl -X GET http://localhost:8081/api/protected \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

4. 刷新令牌示例

func refreshTokenHandler(c *gin.Context) {
    var req struct {
        RefreshToken string `json:"refresh_token"`
    }
    
    if err := c.BindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
        return
    }

    ctx := context.Background()
    token, err := keycloakClient.RefreshToken(ctx, req.RefreshToken, config.ClientID, config.ClientSecret, config.Realm)
    if err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Token refresh failed"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "access_token":  token.AccessToken,
        "refresh_token": token.RefreshToken,
    })
}

这个实现提供了完整的Keycloak集成,包括令牌验证、用户信息获取和令牌刷新功能。所有受保护的API端点都会自动验证Bearer令牌的有效性。

回到顶部