我在Go中创建了一个REST API,这是一个必要的授权层,对于这一层,我尝试使用Keycloak。API将由第三方后端服务使用,有谁知道集成Go客户端和keycloak的工作流程,或者已经实现了吗?我想出了一个名为Gocloak的适配器,但在它的文档中没有任何用于此目的的示例。
发布于 2020-12-18 21:28:48
我有一个很有希望的好答案给你,因为我已经在专业和个人项目中做到了这一点。我给出了关于如何对请求进行身份验证的详细信息,并暗示授权,因为这是更特定于项目的。
你可以使用两种方法,如果你使用Golang + JWTs,我称之为“被动”和“主动”检查。Keycloak可以配置为将所有授权信息放入它签名的JWTs中。Golang代码将在对JWT进行身份验证后读取此授权信息。
在我开始之前,如果你正在使用Kubernetes + Istio,有一个non-Golang method。这将仅执行身份验证。如果它适合您的用例,那么它应该是理想的解决方案,因为它将涵盖名称空间,并且您不必为其编写代码。
被动检查
它不会每次都涉及到Keycloak服务器的网络活动。首先,您需要找到Keycloak应用程序的JWKS URL。这是official docs上的页面。您可以使用Ctrl + F来查看有关Keycloak + JWKS的详细信息。
在解析和验证令牌之后,您可以提取声明。然后,您可以提取授权所需的信息。
优点:每次有JWT时,Golang代码和Keycloak之间没有网络活动。
缺点:如果Keycloak撤销了某人的权限,Golang代码将继续授权请求,直到所有JWTs过期。(最长应为60秒或更短。)
如果您的安全需求允许,我将使用此方法。
我已经从我的原始答案更新了项目,以使用此方法。这是a link to that function。
这是example from my package repo。
package main
import (
"log"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/MicahParks/keyfunc"
)
func main() {
// Get the JWKS URL.
//
// This is a local Keycloak JWKS endpoint for the master realm.
jwksURL := "http://localhost:8080/auth/realms/master/protocol/openid-connect/certs"
// Create the keyfunc options. Refresh the JWKS every hour and log errors.
refreshInterval := time.Hour
options := keyfunc.Options{
RefreshInterval: &refreshInterval,
RefreshErrorHandler: func(err error) {
log.Printf("There was an error with the jwt.KeyFunc\nError: %s", err.Error())
},
}
// Create the JWKS from the resource at the given URL.
jwks, err := keyfunc.Get(jwksURL, options)
if err != nil {
log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
}
// Get a JWT to parse.
jwtB64 := "eyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeDFGbWF5UDJZQnR4YXFTMVNLSlJKR2lYUktudzJvdjVXbVlJTUctQkxFIn0.eyJleHAiOjE2MTU0MDY5ODIsImlhdCI6MTYxNTQwNjkyMiwianRpIjoiMGY2NGJjYTktYjU4OC00MWFhLWFkNDEtMmFmZDM2OGRmNTFkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhZDEyOGRmMS0xMTQwLTRlNGMtYjA5Ny1hY2RjZTcwNWJkOWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0b2tlbmRlbG1lIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjE3Mi4yMC4wLjEiLCJjbGllbnRJZCI6InRva2VuZGVsbWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10b2tlbmRlbG1lIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMC4wLjEifQ.Rxrq41AxbWKIQHWv-Tkb7rqwel3sKT_R_AGvn9mPIHqhw1m7nsQWcL9t2a_8MI2hCwgWtYdgTF1xxBNmb2IW3CZkML5nGfcRrFvNaBHd3UQEqbFKZgnIX29h5VoxekyiwFaGD-0RXL83jF7k39hytEzTatwoVjZ-frga0KFl-nLce3OwncRXVCGmxoFzUsyu9TQFS2Mm_p0AMX1y1MAX1JmLC3WFhH3BohhRqpzBtjSfs_f46nE1-HKjqZ1ERrAc2fmiVJjmG7sT702JRuuzrgUpHlMy2juBG4DkVcMlj4neJUmCD1vZyZBRggfaIxNkwUhHtmS2Cp9tOcwNu47tSg"
// Parse the JWT.
token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
if err != nil {
log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
}
// Check if the token is valid.
if !token.Valid {
log.Fatalf("The token is not valid.")
}
log.Println("The token is valid.")
}主动检查
此方法将在每次JWT进入时与您的Keycloak服务器进行网络活动。它与Keycloak提供的REST端点通信,并且应该让您知道是否有任何JWTs在到期前被撤销。它使用gocloak project。示例auth*处理程序是我使用令人惊叹的goswagger project从我的swagger 2.0规范生成的代码。
优点:这里有一个例子。确定传入请求的JWT是否被撤销。
缺点:大量网络流量无法正确扩展。
所有这些GitHub链接都指向一个提交ID,就在我为一个个人项目进行身份验证之前。
为了获得工作所需的信息,您需要一个充当服务帐户的Keycloak客户端。客户端的Access Type应为confidential,Service Accounts Enabled开关应为ON。确保使用底部的按钮方式保存客户端。
快速入门示例:
package configure
import (
"errors"
"strings"
"go.uber.org/zap"
"github.com/Nerzal/gocloak/v7"
"github.com/MicahParks/terse-URL/models"
)
const (
// headerPrefix is the prefix the JWT has in the "Authorization" header's value.
headerPrefix = "Bearer "
)
var (
// ErrJWTExpired indicates the JWT has expired.
ErrJWTExpired = errors.New("JWT has expired")
)
type KeycloakInfo struct {
BaseURL string
ClientID string
ClientSecret string
Realm string
}
func HandleAuth(keycloakInfo *KeycloakInfo, logger *zap.SugaredLogger) (authHandler func(headerValue string) (jwtInfo *models.JWTInfo, err error), err error) {
// Create the Keycloak client.
keycloak := gocloak.NewClient(keycloakInfo.BaseURL)
// Create a context for logging into Keycloak.
ctx, cancel := DefaultCtx()
defer cancel()
// Log into Keycloak with the given client.
if _, err = keycloak.LoginClient(ctx, keycloakInfo.ClientID, keycloakInfo.ClientSecret, keycloakInfo.Realm); err != nil {
return nil, err
}
// Create the authentication handler via a closure.
return func(headerValue string) (jwtInfo *models.JWTInfo, err error) {
// Strip the prefix from the header.
headerValue = strings.TrimPrefix(headerValue, headerPrefix)
// Create the JWT info structure that will be passed to the endpoints.
jwtInfo = &models.JWTInfo{}
// Create a context to authorize this JWT.
ctx, cancel := DefaultCtx()
defer cancel()
// Check the JWT with Keycloak.
var res *gocloak.RetrospecTokenResult
if res, err = keycloak.RetrospectToken(ctx, headerValue, keycloakInfo.ClientID, keycloakInfo.ClientSecret, keycloakInfo.Realm); err != nil {
logger.Errorw("Failed to retrospect token with Keycloak.",
"error", err.Error(),
)
return nil, err
}
// Confirm the JWT is active.
if !*res.Active {
logger.Warnw("An inactive JWT was received.",
"JWT", headerValue,
)
return nil, ErrJWTExpired
}
// TODO Populate the JWT information in the return value.
return jwtInfo, nil
}, nil
}https://stackoverflow.com/questions/65351977
复制相似问题