Golang微服务中LDAP认证的实现

Golang微服务中LDAP认证的实现 我们如何为 Go 微服务实现 LDAP 认证?在 GO-KIT 中有相关功能吗?还是我们需要使用 JWT?

2 回复

LDAP 并未内置在标准库中。我曾使用过这个库:https://github.com/go-ldap/ldap

更多关于Golang微服务中LDAP认证的实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go微服务中实现LDAP认证,通常结合JWT进行会话管理。GO-KIT本身不提供LDAP集成,但可以配合相关库实现。以下是完整实现方案:

1. 基础LDAP认证实现

package ldapauth

import (
    "crypto/tls"
    "fmt"
    "log"

    "github.com/go-ldap/ldap/v3"
)

type LDAPConfig struct {
    Host     string
    Port     int
    BaseDN   string
    BindDN   string
    BindPass string
    UseTLS   bool
}

type LDAPAuthenticator struct {
    config *LDAPConfig
}

func NewLDAPAuthenticator(config *LDAPConfig) *LDAPAuthenticator {
    return &LDAPAuthenticator{config: config}
}

func (la *LDAPAuthenticator) Authenticate(username, password string) (bool, map[string]interface{}, error) {
    // 建立连接
    var conn *ldap.Conn
    var err error
    
    addr := fmt.Sprintf("%s:%d", la.config.Host, la.config.Port)
    
    if la.config.UseTLS {
        conn, err = ldap.DialTLS("tcp", addr, &tls.Config{
            InsecureSkipVerify: true, // 生产环境应使用有效证书
        })
    } else {
        conn, err = ldap.Dial("tcp", addr)
    }
    
    if err != nil {
        return false, nil, fmt.Errorf("LDAP连接失败: %v", err)
    }
    defer conn.Close()

    // 管理员绑定(用于搜索用户)
    err = conn.Bind(la.config.BindDN, la.config.BindPass)
    if err != nil {
        return false, nil, fmt.Errorf("LDAP管理员绑定失败: %v", err)
    }

    // 搜索用户
    searchRequest := ldap.NewSearchRequest(
        la.config.BaseDN,
        ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
        fmt.Sprintf("(uid=%s)", username),
        []string{"dn", "cn", "mail", "memberOf"},
        nil,
    )

    sr, err := conn.Search(searchRequest)
    if err != nil {
        return false, nil, fmt.Errorf("用户搜索失败: %v", err)
    }

    if len(sr.Entries) != 1 {
        return false, nil, fmt.Errorf("用户不存在或存在多个匹配")
    }

    userDN := sr.Entries[0].DN
    
    // 使用用户凭证绑定验证
    err = conn.Bind(userDN, password)
    if err != nil {
        return false, nil, fmt.Errorf("用户认证失败: %v", err)
    }

    // 提取用户信息
    userInfo := map[string]interface{}{
        "dn":   userDN,
        "cn":   sr.Entries[0].GetAttributeValue("cn"),
        "mail": sr.Entries[0].GetAttributeValue("mail"),
    }
    
    // 获取用户组信息
    groups := sr.Entries[0].GetAttributeValues("memberOf")
    if len(groups) > 0 {
        userInfo["groups"] = groups
    }

    return true, userInfo, nil
}

2. 结合JWT的微服务认证中间件

package auth

import (
    "context"
    "encoding/json"
    "net/http"
    "strings"
    "time"

    "github.com/dgrijalva/jwt-go"
    "github.com/go-kit/kit/endpoint"
    "github.com/go-kit/kit/transport/http"
)

type JWTConfig struct {
    SecretKey     string
    TokenDuration time.Duration
}

type Claims struct {
    Username string                 `json:"username"`
    UserInfo map[string]interface{} `json:"user_info"`
    jwt.StandardClaims
}

type AuthService struct {
    ldapAuth *ldapauth.LDAPAuthenticator
    jwtCfg   *JWTConfig
}

func NewAuthService(ldapAuth *ldapauth.LDAPAuthenticator, jwtCfg *JWTConfig) *AuthService {
    return &AuthService{
        ldapAuth: ldapAuth,
        jwtCfg:   jwtCfg,
    }
}

// 登录端点
func (s *AuthService) MakeLoginEndpoint() endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (interface{}, error) {
        req := request.(LoginRequest)
        
        // LDAP认证
        authenticated, userInfo, err := s.ldapAuth.Authenticate(req.Username, req.Password)
        if err != nil || !authenticated {
            return LoginResponse{
                Success: false,
                Error:   "认证失败",
            }, nil
        }

        // 生成JWT
        expirationTime := time.Now().Add(s.jwtCfg.TokenDuration)
        claims := &Claims{
            Username: req.Username,
            UserInfo: userInfo,
            StandardClaims: jwt.StandardClaims{
                ExpiresAt: expirationTime.Unix(),
                IssuedAt:  time.Now().Unix(),
            },
        }

        token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
        tokenString, err := token.SignedString([]byte(s.jwtCfg.SecretKey))
        if err != nil {
            return LoginResponse{
                Success: false,
                Error:   "令牌生成失败",
            }, nil
        }

        return LoginResponse{
            Success: true,
            Token:   tokenString,
            User:    userInfo,
        }, nil
    }
}

// JWT验证中间件
func JWTMiddleware(secretKey string) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, request interface{}) (interface{}, error) {
            // 从context获取token(实际应从HTTP头获取)
            tokenStr, ok := ctx.Value("jwt_token").(string)
            if !ok {
                return nil, fmt.Errorf("未提供认证令牌")
            }

            claims := &Claims{}
            token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
                return []byte(secretKey), nil
            })

            if err != nil || !token.Valid {
                return nil, fmt.Errorf("无效的认证令牌")
            }

            // 将用户信息添加到context
            ctx = context.WithValue(ctx, "username", claims.Username)
            ctx = context.WithValue(ctx, "user_info", claims.UserInfo)

            return next(ctx, request)
        }
    }
}

// HTTP传输层中间件
func AuthHTTPMiddleware(secretKey string) http.RequestFunc {
    return func(ctx context.Context, r *http.Request) context.Context {
        authHeader := r.Header.Get("Authorization")
        if authHeader == "" {
            return ctx
        }

        // 提取Bearer token
        parts := strings.Split(authHeader, " ")
        if len(parts) != 2 || parts[0] != "Bearer" {
            return ctx
        }

        // 验证token
        claims := &Claims{}
        token, err := jwt.ParseWithClaims(parts[1], claims, func(token *jwt.Token) (interface{}, error) {
            return []byte(secretKey), nil
        })

        if err == nil && token.Valid {
            ctx = context.WithValue(ctx, "jwt_token", parts[1])
            ctx = context.WithValue(ctx, "username", claims.Username)
            ctx = context.WithValue(ctx, "user_info", claims.UserInfo)
        }

        return ctx
    }
}

3. GO-KIT集成示例

package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "time"

    "github.com/go-kit/kit/endpoint"
    httptransport "github.com/go-kit/kit/transport/http"
    "github.com/gorilla/mux"
)

func main() {
    // LDAP配置
    ldapConfig := &ldapauth.LDAPConfig{
        Host:     os.Getenv("LDAP_HOST"),
        Port:     389,
        BaseDN:   "dc=example,dc=com",
        BindDN:   "cn=admin,dc=example,dc=com",
        BindPass: os.Getenv("LDAP_ADMIN_PASS"),
        UseTLS:   false,
    }

    // JWT配置
    jwtConfig := &JWTConfig{
        SecretKey:     os.Getenv("JWT_SECRET"),
        TokenDuration: 24 * time.Hour,
    }

    // 创建认证服务
    ldapAuth := ldapauth.NewLDAPAuthenticator(ldapConfig)
    authService := NewAuthService(ldapAuth, jwtConfig)

    // 创建端点
    loginEndpoint := authService.MakeLoginEndpoint()
    
    // 受保护的业务端点(示例)
    protectedEndpoint := MakeProtectedEndpoint()
    protectedEndpoint = JWTMiddleware(jwtConfig.SecretKey)(protectedEndpoint)

    // HTTP处理器
    loginHandler := httptransport.NewServer(
        loginEndpoint,
        decodeLoginRequest,
        encodeResponse,
    )

    protectedHandler := httptransport.NewServer(
        protectedEndpoint,
        decodeProtectedRequest,
        encodeResponse,
        httptransport.ServerBefore(AuthHTTPMiddleware(jwtConfig.SecretKey)),
    )

    // 路由设置
    r := mux.NewRouter()
    r.Handle("/api/login", loginHandler).Methods("POST")
    r.Handle("/api/protected", protectedHandler).Methods("GET")

    fmt.Println("服务启动在 :8080")
    http.ListenAndServe(":8080", r)
}

// 请求/响应结构
type LoginRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

type LoginResponse struct {
    Success bool                   `json:"success"`
    Token   string                 `json:"token,omitempty"`
    User    map[string]interface{} `json:"user,omitempty"`
    Error   string                 `json:"error,omitempty"`
}

// 解码器
func decodeLoginRequest(_ context.Context, r *http.Request) (interface{}, error) {
    var req LoginRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        return nil, err
    }
    return req, nil
}

4. 环境变量配置示例

# .env文件
LDAP_HOST=ldap.example.com
LDAP_ADMIN_PASS=admin_password
JWT_SECRET=your-256-bit-secret-key-here

这个实现方案中,LDAP负责用户凭证验证,JWT负责无状态会话管理。GO-KIT通过中间件机制集成认证逻辑,保持微服务的可测试性和可维护性。生产环境需要添加TLS加密、连接池、令牌刷新机制和更完善的错误处理。

回到顶部