openid-connect鉴权完成

This commit is contained in:
Liujian
2024-01-29 22:28:15 +08:00
parent c4daea2f5e
commit 86f8627082
19 changed files with 931 additions and 218 deletions

View File

@@ -1,6 +1,7 @@
package main
import (
"github.com/eolinker/apinto/drivers/plugins/acl"
"github.com/eolinker/apinto/drivers/plugins/app"
"github.com/eolinker/apinto/drivers/plugins/cors"
data_transform "github.com/eolinker/apinto/drivers/plugins/data-transform"
@@ -93,6 +94,7 @@ func pluginRegister(extenderRegister eosc.IExtenderDriverRegister) {
app.Register(extenderRegister)
rsa_filter.Register(extenderRegister)
js_inject.Register(extenderRegister)
acl.Register(extenderRegister)
// 可观测性(输出内容到第三方)
access_log.Register(extenderRegister)

View File

@@ -20,7 +20,7 @@ type IClient interface {
Expire() int64
}
func registerClient(clientId string, client IClient) {
func RegisterClient(clientId string, client IClient) {
manager.clients.Set(clientId, client)
}
@@ -32,15 +32,36 @@ func GetClient(clientId string) (IClient, bool) {
return manager.clients.Get(clientId)
}
func GetClientMap(id string) map[string]struct{} {
result := map[string]struct{}{}
apps, has := manager.apps.Get(id)
if !has {
return result
}
for key := range apps {
result[key] = struct{}{}
}
return result
}
func SetClientMap(id string, clientIds map[string]struct{}) {
manager.apps.Set(id, clientIds)
}
func DeleteClientMap(id string) (map[string]struct{}, bool) {
return manager.apps.Del(id)
}
var manager = NewManager()
// Manager 管理oauth2配置
type Manager struct {
clients eosc.Untyped[string, IClient]
apps eosc.Untyped[string, map[string]struct{}]
}
func NewManager() *Manager {
return &Manager{clients: eosc.BuildUntyped[string, IClient]()}
return &Manager{clients: eosc.BuildUntyped[string, IClient](), apps: eosc.BuildUntyped[string, map[string]struct{}]()}
}
type client struct {

View File

@@ -74,6 +74,8 @@ func (o *oauth2) Check(appID string, users []application.ITransformConfig) error
func (o *oauth2) Set(app application.IApp, users []application.ITransformConfig) {
infos := make([]*application.UserInfo, 0, len(users))
clientIDMap := make(map[string]struct{})
oldClientIDMap := GetClientMap(app.Id())
for _, user := range users {
v, _ := user.Config().(*User)
c := &client{
@@ -93,8 +95,9 @@ func (o *oauth2) Set(app application.IApp, users []application.ITransformConfig)
log.Debug("hash rule: ", *hr)
c.hashRule = hr
}
registerClient(v.Pattern.ClientId, c)
RegisterClient(v.Pattern.ClientId, c)
clientIDMap[v.Pattern.ClientId] = struct{}{}
delete(oldClientIDMap, v.Pattern.ClientId)
infos = append(infos, &application.UserInfo{
Name: v.Username(),
Value: v.Pattern.ClientSecret,
@@ -106,11 +109,20 @@ func (o *oauth2) Set(app application.IApp, users []application.ITransformConfig)
App: app,
})
}
for clientID := range oldClientIDMap {
RemoveClient(clientID)
}
SetClientMap(app.Id(), clientIDMap)
o.users.Set(app.Id(), infos)
}
func (o *oauth2) Del(appID string) {
o.users.DelByAppID(appID)
oldClientIDMap, _ := DeleteClientMap(appID)
for clientID := range oldClientIDMap {
RemoveClient(clientID)
}
}
func (o *oauth2) UserCount() int {

View File

@@ -0,0 +1,26 @@
package openid_connect_jwt
import (
"encoding/base64"
"github.com/eolinker/apinto/application"
)
type Config struct {
application.Auth
Users []*User `json:"users" label:"用户列表"`
}
type User struct {
Pattern Pattern `json:"pattern" label:"用户信息"`
application.User
}
type Pattern struct {
Issuer string `json:"issuer"`
AuthenticatedGroupsClaim []string `json:"authenticated_groups_claim"`
}
func (u *User) Username() string {
return base64.RawStdEncoding.EncodeToString([]byte(u.Pattern.Issuer))
}

View File

@@ -0,0 +1,131 @@
package openid_connect_jwt
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/eolinker/eosc/log"
"github.com/lestrrat-go/jwx/jwk"
)
var client = http.Client{
Timeout: 5 * time.Second,
}
type IssuerConfig struct {
ID string `json:"id"`
Issuer string `json:"issuer"`
Configuration *DiscoveryConfig `json:"configuration"`
Keys []JWK `json:"keys"`
UpdateTime time.Time `json:"update_time"`
JWKKeys map[string]jwk.Key `json:"-"`
}
type DiscoveryConfig struct {
TokenEndpoint string `json:"token_endpoint"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
JwksUri string `json:"jwks_uri"`
ResponseModesSupported []string `json:"response_modes_supported"`
SubjectTypesSupported []string `json:"subject_types_supported"`
IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
ResponseTypesSupported []string `json:"response_types_supported"`
ScopesSupported []string `json:"scopes_supported"`
Issuer string `json:"issuer"`
MicrosoftMultiRefreshToken bool `json:"microsoft_multi_refresh_token"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
HttpLogoutSupported bool `json:"http_logout_supported"`
FrontchannelLogoutSupported bool `json:"frontchannel_logout_supported"`
EndSessionEndpoint string `json:"end_session_endpoint"`
ClaimsSupported []string `json:"claims_supported"`
CheckSessionIframe string `json:"check_session_iframe"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
KerberosEndpoint string `json:"kerberos_endpoint"`
TenantRegionScope string `json:"tenant_region_scope"`
CloudInstanceName string `json:"cloud_instance_name"`
CloudGraphHostName string `json:"cloud_graph_host_name"`
MsgraphHost string `json:"msgraph_host"`
RbacUrl string `json:"rbac_url"`
}
type JWKs struct {
Keys []JWK `json:"keys"`
}
type JWK struct {
Kid string `json:"kid"`
Kty string `json:"kty"`
Alg string `json:"alg"`
Use string `json:"use"`
N string `json:"n"`
E string `json:"e"`
X5C []string `json:"x5c"`
X5T string `json:"x5t"`
X5TS256 string `json:"x5t#S256"`
}
func getIssuerConfig(issuer string) (*DiscoveryConfig, error) {
resp, err := client.Get(issuer)
if err != nil {
return nil, fmt.Errorf("get issuer config error: %w, issuer: %s", err, issuer)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read issuer config error: %w, issuer: %s", err, issuer)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("get issuer config error: %d, issuer: %s, body: %s", resp.StatusCode, issuer, string(body))
}
var config DiscoveryConfig
err = json.Unmarshal(body, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal issuer config error: %w, issuer: %s, body: %s", err, issuer, string(body))
}
return &config, nil
}
func getJWKs(uri string) ([]JWK, map[string]jwk.Key, error) {
resp, err := client.Get(uri)
if err != nil {
return nil, nil, fmt.Errorf("get issuer jwks error: %w, uri: %s", err, uri)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("read issuer jwks error: %w, uri: %s", err, uri)
}
if resp.StatusCode != 200 {
return nil, nil, fmt.Errorf("get issuer jwks error: %d, uri: %s, body: %s", resp.StatusCode, uri, string(body))
}
var jwks JWKs
err = json.Unmarshal(body, &jwks)
if err != nil {
return nil, nil, fmt.Errorf("unmarshal issuer jwks error: %w, uri: %s, body: %s", err, uri, string(body))
}
set, err := jwk.Parse(body)
if err != nil {
return nil, nil, fmt.Errorf("parse issuer jwks error: %w, uri: %s, body: %s", err, uri, string(body))
}
keys := make(map[string]jwk.Key)
l := set.Len()
for i := 0; i < l; i++ {
key, success := set.Get(i)
if !success {
continue
}
if key.KeyUsage() != string(jwk.ForSignature) {
continue
}
pubKey, err := key.PublicKey()
if err != nil {
log.Errorf("get public key error: %w, uri: %s, key: %s", err, uri, key.KeyID())
}
keys[key.KeyID()] = pubKey
}
return jwks.Keys, keys, nil
}

View File

@@ -0,0 +1,79 @@
package openid_connect_jwt
import (
"fmt"
"reflect"
"github.com/eolinker/eosc/router"
"github.com/eolinker/eosc/utils/schema"
"github.com/eolinker/apinto/application"
"github.com/eolinker/apinto/application/auth"
)
var _ auth.IAuthFactory = (*factory)(nil)
var driverName = "openid-connect-jwt"
// Register 注册auth驱动工厂
func Register() {
auth.FactoryRegister(driverName, NewFactory())
}
type factory struct {
configType reflect.Type
render *schema.Schema
userType reflect.Type
}
func (f *factory) Render() interface{} {
return f.render
}
func (f *factory) ConfigType() reflect.Type {
return f.configType
}
func (f *factory) UserType() reflect.Type {
return f.userType
}
func (f *factory) Alias() []string {
return []string{
"openid-connect-jwt",
}
}
func (f *factory) PreRouters() []*auth.PreRouter {
return []*auth.PreRouter{}
}
func (f *factory) Create(tokenName string, position string, rule interface{}) (application.IAuth, error) {
a := &jwt{
id: toId(tokenName, position),
tokenName: tokenName,
position: position,
users: application.NewUserManager(),
}
return a, nil
}
// NewFactory 生成一个 auth_apiKey工厂
func NewFactory() auth.IAuthFactory {
typ := reflect.TypeOf((*Config)(nil))
render, _ := schema.Generate(typ, nil)
h := newIssuerHandler("/openid-connect/issuers")
j := newJwkHandler("/openid-connect/jwks")
router.SetPath("openid-connect-jwt-issuer", h.prefix, h)
router.SetPath("openid-connect-jwt-jwk", j.prefix, j)
return &factory{
configType: typ,
render: render,
userType: reflect.TypeOf((*User)(nil)),
}
}
func toId(tokenName, position string) string {
return fmt.Sprintf("%s@%s@%s", tokenName, position, driverName)
}

View File

@@ -0,0 +1,105 @@
package openid_connect_jwt
import (
"encoding/json"
"fmt"
"net/http"
"strings"
)
type issuerHandler struct {
prefix string
}
func newIssuerHandler(prefix string) *issuerHandler {
return &issuerHandler{prefix: strings.TrimPrefix(prefix, "/")}
}
func (h *issuerHandler) PrefixPath() string {
return h.prefix
}
func (h *issuerHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
path := strings.TrimPrefix(request.URL.Path, fmt.Sprintf("/apinto/%s", h.prefix))
switch request.Method {
case http.MethodGet:
if path == "" {
h.list(writer, request)
return
}
paths := strings.SplitN(path, "/", 2)
if len(paths) == 2 {
h.info(writer, request, paths[1])
return
}
writer.WriteHeader(http.StatusNotFound)
writer.Write([]byte("not found"))
return
default:
writer.WriteHeader(http.StatusMethodNotAllowed)
writer.Write([]byte("method not allowed"))
return
}
}
func (h *issuerHandler) info(writer http.ResponseWriter, request *http.Request, id string) {
var body []byte
info, has := manager.Issuers.Get(id)
if !has {
body = []byte("{\"message\":\"not found\"}")
} else {
body, _ = json.Marshal(info)
}
writer.Write(body)
}
func (h *issuerHandler) list(writer http.ResponseWriter, request *http.Request) {
var data = map[string]interface{}{
"data": manager.Issuers.List(),
}
body, _ := json.Marshal(data)
writer.Write(body)
}
type jwkHandler struct {
prefix string
}
func newJwkHandler(prefix string) *jwkHandler {
return &jwkHandler{prefix: strings.TrimPrefix(prefix, "/")}
}
func (j *jwkHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
path := strings.TrimPrefix(request.URL.Path, fmt.Sprintf("/apinto/%s", j.prefix))
switch request.Method {
case http.MethodGet:
if path == "" {
j.list(writer, request)
return
}
writer.WriteHeader(http.StatusNotFound)
writer.Write([]byte("not found"))
return
default:
writer.WriteHeader(http.StatusMethodNotAllowed)
writer.Write([]byte("method not allowed"))
return
}
}
func (j *jwkHandler) list(writer http.ResponseWriter, request *http.Request) {
all := manager.Issuers.List()
keys := make([]JWK, 0, len(all))
for _, issuer := range all {
keys = append(keys, issuer.Keys...)
}
var data = map[string]interface{}{
"keys": keys,
}
body, _ := json.Marshal(data)
writer.Write(body)
}

View File

@@ -0,0 +1,131 @@
package openid_connect_jwt
import (
"fmt"
"strings"
"sync"
"github.com/ohler55/ojg/jp"
"github.com/eolinker/apinto/resources"
scope_manager "github.com/eolinker/apinto/scope-manager"
"github.com/eolinker/apinto/application"
http_service "github.com/eolinker/eosc/eocontext/http-context"
"github.com/eolinker/eosc/log"
)
var _ application.IAuth = (*jwt)(nil)
type jwt struct {
id string
tokenName string
position string
users application.IUserManager
cache scope_manager.IProxyOutput[resources.ICache]
once sync.Once
}
func (o *jwt) GetUser(ctx http_service.IHttpContext) (*application.UserInfo, bool) {
token, has := application.GetToken(ctx, o.tokenName, o.position)
if !has || token == "" {
return nil, false
}
id, obj, has := verify(token)
if !has {
return nil, false
}
info, has := o.users.Get(id)
if !has {
return nil, false
}
exprs, ok := info.Additional.([]jp.Expr)
if !ok {
return nil, false
}
result := make([]interface{}, 0, len(exprs))
for _, expr := range exprs {
v := expr.Get(obj)
if len(v) > 0 {
result = append(result, v...)
}
}
ctx.WithValue("acl_groups", result)
return info, true
}
func (o *jwt) ID() string {
return o.id
}
func (o *jwt) Driver() string {
return driverName
}
func (o *jwt) Check(appID string, users []application.ITransformConfig) error {
us := make([]application.IUser, 0, len(users))
for _, u := range users {
v, ok := u.Config().(*User)
if !ok {
return fmt.Errorf("%s check error: invalid config type", driverName)
}
us = append(us, v)
}
return o.users.Check(appID, driverName, us)
}
func (o *jwt) Set(app application.IApp, users []application.ITransformConfig) {
infos := make([]*application.UserInfo, 0, len(users))
idMap := make(map[string]struct{})
oldIDMap := manager.GetIssuerIDMap(app.Id())
for _, user := range users {
v, _ := user.Config().(*User)
exprs := make([]jp.Expr, 0, len(v.Pattern.AuthenticatedGroupsClaim))
for _, key := range v.Pattern.AuthenticatedGroupsClaim {
if !strings.HasPrefix(key, "$.") {
key = fmt.Sprintf("$.%s", key)
}
expr, err := jp.ParseString(key)
if err != nil {
log.Errorf("parse key %w, key: %s", err, key)
continue
}
exprs = append(exprs, expr)
}
manager.Set(v.Username(), &IssuerConfig{
ID: v.Username(),
Issuer: v.Pattern.Issuer,
})
delete(oldIDMap, v.Username())
idMap[v.Username()] = struct{}{}
infos = append(infos, &application.UserInfo{
Name: v.Username(),
Value: strings.Join(v.Pattern.AuthenticatedGroupsClaim, ","),
Expire: v.Expire,
Labels: v.Labels,
HideCredential: v.HideCredential,
TokenName: o.tokenName,
Position: o.position,
App: app,
Additional: exprs,
})
}
for id := range oldIDMap {
manager.Del(id)
}
manager.SetIssuerIDMap(app.Id(), idMap)
o.users.Set(app.Id(), infos)
}
func (o *jwt) Del(appID string) {
o.users.DelByAppID(appID)
oldIDMap, _ := manager.DelIssuerIDMap(appID)
for id := range oldIDMap {
manager.Del(id)
}
}
func (o *jwt) UserCount() int {
return o.users.Count()
}

View File

@@ -0,0 +1,77 @@
package openid_connect_jwt
import (
"time"
"github.com/eolinker/eosc"
"github.com/eolinker/eosc/log"
)
var (
manager = NewManager()
)
type Manager struct {
Issuers eosc.Untyped[string, *IssuerConfig]
Apps eosc.Untyped[string, map[string]struct{}]
}
func NewManager() *Manager {
m := &Manager{Issuers: eosc.BuildUntyped[string, *IssuerConfig](), Apps: eosc.BuildUntyped[string, map[string]struct{}]()}
go m.doLoop()
return m
}
func (m *Manager) doLoop() {
ticket := time.NewTicker(10 * time.Second)
defer ticket.Stop()
for {
select {
case <-ticket.C:
for _, issuer := range m.Issuers.All() {
config, err := getIssuerConfig(issuer.Issuer)
if err != nil {
log.Error(err)
continue
}
issuer.Configuration = config
issuer.UpdateTime = time.Now()
keys, jwks, err := getJWKs(config.JwksUri)
if err != nil {
log.Errorf("%w, issuer: %s", err, issuer.Issuer)
continue
}
issuer.Keys = keys
issuer.JWKKeys = jwks
}
}
}
}
func (m *Manager) Set(id string, config *IssuerConfig) {
m.Issuers.Set(id, config)
}
func (m *Manager) Del(id string) {
m.Issuers.Del(id)
}
func (m *Manager) GetIssuerIDMap(appID string) map[string]struct{} {
result := make(map[string]struct{})
idMap, has := m.Apps.Get(appID)
if !has {
return result
}
for id := range idMap {
result[id] = struct{}{}
}
return result
}
func (m *Manager) SetIssuerIDMap(appID string, issuerIDMap map[string]struct{}) {
m.Apps.Set(appID, issuerIDMap)
}
func (m *Manager) DelIssuerIDMap(appID string) (map[string]struct{}, bool) {
return m.Apps.Del(appID)
}

View File

@@ -0,0 +1,35 @@
package openid_connect_jwt
import (
"encoding/base64"
"encoding/json"
"errors"
"strings"
)
var (
ErrInvalidToken = errors.New("invalid token")
)
type tokenHeader struct {
Alg string `json:"alg"`
Kid string `json:"kid"`
Typ string `json:"typ"`
}
func extractTokenHeader(token string) (*tokenHeader, error) {
ts := strings.Split(token, ".")
if len(ts) != 3 {
return nil, ErrInvalidToken
}
headerData, err := base64.RawStdEncoding.DecodeString(ts[0])
if err != nil {
return nil, err
}
var th tokenHeader
err = json.Unmarshal(headerData, &th)
if err != nil {
return nil, err
}
return &th, nil
}

View File

@@ -0,0 +1,120 @@
package openid_connect_jwt
import (
"fmt"
"time"
"github.com/lestrrat-go/jwx/jws"
"github.com/eolinker/eosc/log"
"github.com/lestrrat-go/jwx/jwa"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
)
type IVerifyClaim interface {
Verify(obj interface{}) error
}
var claims = []IVerifyClaim{
newNbfClaim(),
newExpClaim(),
}
func newNbfClaim() IVerifyClaim {
nbfExpr, _ := jp.ParseString("$.nbf")
return &nbfClaim{nbfExpr}
}
type nbfClaim struct {
expr jp.Expr
}
func (n *nbfClaim) Verify(obj interface{}) error {
result := n.expr.Get(obj)
if len(result) == 0 {
return nil
}
var nbf int64
switch r := result[0].(type) {
case float64:
nbf = int64(r)
case int64:
nbf = r
default:
return fmt.Errorf("nbf claim type error")
}
if nbf > time.Now().Unix() {
return fmt.Errorf("token not valid yet")
}
return nil
}
func newExpClaim() IVerifyClaim {
expExpr, _ := jp.ParseString("$.exp")
return &expClaim{expExpr}
}
type expClaim struct {
expr jp.Expr
}
func (e *expClaim) Verify(obj interface{}) error {
result := e.expr.Get(obj)
if len(result) == 0 {
return nil
}
var exp int64
switch r := result[0].(type) {
case float64:
exp = int64(r)
case int64:
exp = r
default:
return fmt.Errorf("exp claim type error")
}
if exp < time.Now().Unix() {
return fmt.Errorf("token expired")
}
return nil
}
func verify(token string) (string, interface{}, bool) {
id, payload, success := verifySign(token)
if !success {
return "", nil, false
}
obj, err := oj.Parse(payload)
if err != nil {
log.Errorf("%w, payload: %s", err, string(payload))
return "", nil, false
}
for _, c := range claims {
err = c.Verify(obj)
if err != nil {
log.Errorf("%w, payload: %s", err, string(payload))
return "", nil, false
}
}
return id, obj, true
}
func verifySign(token string) (string, []byte, bool) {
header, err := extractTokenHeader(token)
if err != nil {
return "", nil, false
}
for _, issuer := range manager.Issuers.All() {
if key, ok := issuer.JWKKeys[header.Kid]; ok {
payload, err := jws.Verify([]byte(token), jwa.SignatureAlgorithm(key.Algorithm()), key)
if err != nil {
log.DebugF("%w, issuer: %s, key: %s", err, issuer.Issuer, key.KeyID())
continue
}
log.DebugF("verify sign successful! payload: %s", string(payload))
return issuer.ID, payload, true
}
}
return "", nil, false
}

View File

@@ -25,6 +25,7 @@ type UserInfo struct {
TokenName string
Position string
App IApp
Additional interface{}
}
var _ IUserManager = (*UserManager)(nil)

View File

@@ -3,6 +3,8 @@ package app
import (
"sync"
openid_connect_jwt "github.com/eolinker/apinto/application/auth/openid-connect-jwt"
"github.com/eolinker/apinto/application/auth"
"github.com/eolinker/apinto/application/auth/aksk"
"github.com/eolinker/apinto/application/auth/apikey"
@@ -36,6 +38,7 @@ func NewFactory() eosc.IExtenderDriverFactory {
aksk.Register()
jwt.Register()
oauth2.Register()
openid_connect_jwt.Register()
appManager = manager.NewManager(auth.Alias(), auth.Keys())
bean.Injection(&appManager)
})

View File

@@ -0,0 +1,29 @@
package acl
import (
"github.com/eolinker/apinto/drivers"
"github.com/eolinker/eosc"
)
type Config struct {
Allow []string `json:"allow"`
Deny []string `json:"deny"`
HideGroupsHeader bool `json:"hide_groups_header"`
}
func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) {
allow := make(map[string]struct{})
deny := make(map[string]struct{})
for _, a := range conf.Allow {
allow[a] = struct{}{}
}
for _, d := range conf.Deny {
deny[d] = struct{}{}
}
return &executor{
WorkerBase: drivers.Worker(id, name),
cfg: conf,
allow: allow,
deny: deny,
}, nil
}

View File

@@ -0,0 +1,124 @@
package acl
import (
"encoding/json"
"fmt"
"strings"
"github.com/eolinker/eosc/log"
"github.com/eolinker/apinto/drivers"
"github.com/eolinker/eosc"
"github.com/eolinker/eosc/eocontext"
http_service "github.com/eolinker/eosc/eocontext/http-context"
)
var _ eocontext.IFilter = (*executor)(nil)
var _ http_service.HttpFilter = (*executor)(nil)
var _ eosc.IWorker = (*executor)(nil)
type executor struct {
drivers.WorkerBase
cfg *Config
allow map[string]struct{}
deny map[string]struct{}
}
func (e *executor) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err error) {
return http_service.DoHttpFilter(e, ctx, next)
}
func (e *executor) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) (err error) {
groupMap := make(map[string]struct{})
groups := ctx.Value("acl_groups")
groupArr := make([]string, 0)
switch gs := groups.(type) {
case []interface{}:
for _, g := range gs {
switch t := g.(type) {
case string:
groupMap[t] = struct{}{}
groupArr = append(groupArr, t)
case []interface{}:
for _, v := range t {
if s, ok := v.(string); ok {
groupMap[s] = struct{}{}
groupArr = append(groupArr, s)
}
}
}
}
case []string:
for _, g := range gs {
groupMap[g] = struct{}{}
groupArr = append(groupArr, g)
}
case string:
groupMap[gs] = struct{}{}
groupArr = append(groupArr, gs)
}
type Msg struct {
Message string `json:"message"`
}
for key := range e.deny {
if _, ok := groupMap[key]; ok {
// 拒绝访问
err = fmt.Errorf("acl deny, group is %s", key)
log.Error(err)
msg := Msg{
Message: "Unauthorized",
}
data, _ := json.Marshal(msg)
ctx.Response().SetStatus(401, "Unauthorized")
ctx.Response().SetBody([]byte(data))
return
}
}
allow := false
for key := range e.allow {
if _, ok := groupMap[key]; ok {
allow = true
break
}
}
if !allow {
err = fmt.Errorf("groups is not allow, groups is %v", groups)
log.Error(err)
msg := Msg{
Message: "Unauthorized",
}
data, _ := json.Marshal(msg)
ctx.Response().SetStatus(401, "Unauthorized")
ctx.Response().SetBody([]byte(data))
return
}
if !e.cfg.HideGroupsHeader {
ctx.Proxy().Header().SetHeader("X-Consumer-Groups", strings.Join(groupArr, ","))
}
if next != nil {
return next.DoChain(ctx)
}
return
}
func (e *executor) Destroy() {
return
}
func (e *executor) Start() error {
return nil
}
func (e *executor) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error {
return nil
}
func (e *executor) Stop() error {
e.Destroy()
return nil
}
func (e *executor) CheckSkill(skill string) bool {
return http_service.FilterSkillName == skill
}

View File

@@ -0,0 +1,18 @@
package acl
import (
"github.com/eolinker/apinto/drivers"
"github.com/eolinker/eosc"
)
const (
Name = "acl"
)
func Register(register eosc.IExtenderDriverRegister) {
register.RegisterExtenderDriver(Name, NewFactory())
}
func NewFactory() eosc.IExtenderDriverFactory {
return drivers.NewFactory[Config](Create)
}

View File

@@ -30,7 +30,7 @@ func (a *App) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain)
log.Debug("auth beginning")
err := a.auth(ctx)
if err != nil {
ctx.Response().SetStatus(403, "403")
ctx.Response().SetStatus(401, "Unauthorized")
ctx.Response().SetBody([]byte(err.Error()))
return err
}

View File

@@ -1,208 +0,0 @@
package openid_connect
type Config struct {
Anonymous interface{} `json:"anonymous"`
Audience []interface{} `json:"audience"`
AudienceClaim []interface{} `json:"audience_claim"`
AudienceRequired []interface{} `json:"audience_required"`
AuthMethods []string `json:"auth_methods"`
AuthenticatedGroupsClaim []string `json:"authenticated_groups_claim"`
AuthorizationCookieDomain interface{} `json:"authorization_cookie_domain"`
AuthorizationCookieHttponly bool `json:"authorization_cookie_httponly"`
AuthorizationCookieLifetime int `json:"authorization_cookie_lifetime"`
AuthorizationCookieName string `json:"authorization_cookie_name"`
AuthorizationCookiePath string `json:"authorization_cookie_path"`
AuthorizationCookieSamesite string `json:"authorization_cookie_samesite"`
AuthorizationCookieSecure interface{} `json:"authorization_cookie_secure"`
AuthorizationEndpoint interface{} `json:"authorization_endpoint"`
AuthorizationQueryArgsClient interface{} `json:"authorization_query_args_client"`
AuthorizationQueryArgsNames interface{} `json:"authorization_query_args_names"`
AuthorizationQueryArgsValues interface{} `json:"authorization_query_args_values"`
BearerTokenCookieName interface{} `json:"bearer_token_cookie_name"`
BearerTokenParamType []string `json:"bearer_token_param_type"`
CacheIntrospection bool `json:"cache_introspection"`
CacheTokenExchange bool `json:"cache_token_exchange"`
CacheTokens bool `json:"cache_tokens"`
CacheTokensSalt interface{} `json:"cache_tokens_salt"`
CacheTtl int `json:"cache_ttl"`
CacheTtlMax interface{} `json:"cache_ttl_max"`
CacheTtlMin interface{} `json:"cache_ttl_min"`
CacheTtlNeg interface{} `json:"cache_ttl_neg"`
CacheTtlResurrect interface{} `json:"cache_ttl_resurrect"`
CacheUserInfo bool `json:"cache_user_info"`
ClientAlg interface{} `json:"client_alg"`
ClientArg string `json:"client_arg"`
ClientAuth interface{} `json:"client_auth"`
ClientCredentialsParamType []string `json:"client_credentials_param_type"`
ClientId []string `json:"client_id"`
ClientJwk interface{} `json:"client_jwk"`
ClientSecret []string `json:"client_secret"`
ConsumerBy []string `json:"consumer_by"`
ConsumerClaim interface{} `json:"consumer_claim"`
ConsumerOptional bool `json:"consumer_optional"`
CredentialClaim []string `json:"credential_claim"`
DiscoveryHeadersNames interface{} `json:"discovery_headers_names"`
DiscoveryHeadersValues interface{} `json:"discovery_headers_values"`
DisplayErrors bool `json:"display_errors"`
Domains interface{} `json:"domains"`
EnableHsSignatures bool `json:"enable_hs_signatures"`
EndSessionEndpoint interface{} `json:"end_session_endpoint"`
ExtraJwksUris interface{} `json:"extra_jwks_uris"`
ForbiddenDestroySession bool `json:"forbidden_destroy_session"`
ForbiddenErrorMessage string `json:"forbidden_error_message"`
ForbiddenRedirectUri interface{} `json:"forbidden_redirect_uri"`
GroupsClaim []string `json:"groups_claim"`
GroupsRequired interface{} `json:"groups_required"`
HideCredentials bool `json:"hide_credentials"`
HttpProxy interface{} `json:"http_proxy"`
HttpProxyAuthorization interface{} `json:"http_proxy_authorization"`
HttpVersion float64 `json:"http_version"`
HttpsProxy interface{} `json:"https_proxy"`
HttpsProxyAuthorization interface{} `json:"https_proxy_authorization"`
IdTokenParamName interface{} `json:"id_token_param_name"`
IdTokenParamType []string `json:"id_token_param_type"`
IgnoreSignature []interface{} `json:"ignore_signature"`
IntrospectJwtTokens bool `json:"introspect_jwt_tokens"`
Issuer string `json:"issuer"`
IssuersAllowed interface{} `json:"issuers_allowed"`
JwtSessionClaim string `json:"jwt_session_claim"`
JwtSessionCookie interface{} `json:"jwt_session_cookie"`
Keepalive bool `json:"keepalive"`
Leeway int `json:"leeway"`
LoginAction string `json:"login_action"`
LoginMethods []string `json:"login_methods"`
LoginRedirectMode string `json:"login_redirect_mode"`
LoginRedirectUri interface{} `json:"login_redirect_uri"`
LoginTokens []string `json:"login_tokens"`
LogoutMethods []string `json:"logout_methods"`
LogoutPostArg interface{} `json:"logout_post_arg"`
LogoutQueryArg interface{} `json:"logout_query_arg"`
LogoutRedirectUri interface{} `json:"logout_redirect_uri"`
LogoutRevoke bool `json:"logout_revoke"`
LogoutRevokeAccessToken bool `json:"logout_revoke_access_token"`
LogoutRevokeRefreshToken bool `json:"logout_revoke_refresh_token"`
LogoutUriSuffix interface{} `json:"logout_uri_suffix"`
MaxAge interface{} `json:"max_age"`
NoProxy interface{} `json:"no_proxy"`
PasswordParamType []string `json:"password_param_type"`
PreserveQueryArgs bool `json:"preserve_query_args"`
RedirectUri interface{} `json:"redirect_uri"`
RediscoveryLifetime int `json:"rediscovery_lifetime"`
RefreshTokenParamName interface{} `json:"refresh_token_param_name"`
RefreshTokenParamType []string `json:"refresh_token_param_type"`
RefreshTokens bool `json:"refresh_tokens"`
ResponseMode string `json:"response_mode"`
ResponseType []string `json:"response_type"`
Reverify bool `json:"reverify"`
RevocationEndpoint interface{} `json:"revocation_endpoint"`
RevocationEndpointAuthMethod interface{} `json:"revocation_endpoint_auth_method"`
RolesClaim []string `json:"roles_claim"`
RolesRequired interface{} `json:"roles_required"`
RunOnPreflight bool `json:"run_on_preflight"`
Scopes []string `json:"scopes"`
ScopesClaim []interface{} `json:"scopes_claim"`
ScopesRequired []interface{} `json:"scopes_required"`
SearchUserInfo bool `json:"search_user_info"`
SessionCompressor string `json:"session_compressor"`
SessionCookieDomain interface{} `json:"session_cookie_domain"`
SessionMemcacheHost string `json:"session_memcache_host"`
SessionMemcachePort int `json:"session_memcache_port"`
SessionMemcachePrefix string `json:"session_memcache_prefix"`
SessionMemcacheSocket interface{} `json:"session_memcache_socket"`
SessionSecret interface{} `json:"session_secret"`
SessionStorage string `json:"session_storage"`
SessionStrategy string `json:"session_strategy"`
SslVerify bool `json:"ssl_verify"`
Timeout int `json:"timeout"`
TokenEndpoint interface{} `json:"token_endpoint"`
TokenEndpointAuthMethod interface{} `json:"token_endpoint_auth_method"`
TokenExchangeEndpoint interface{} `json:"token_exchange_endpoint"`
TokenHeadersClient interface{} `json:"token_headers_client"`
TokenHeadersGrants interface{} `json:"token_headers_grants"`
TokenHeadersNames interface{} `json:"token_headers_names"`
TokenHeadersPrefix interface{} `json:"token_headers_prefix"`
TokenHeadersReplay interface{} `json:"token_headers_replay"`
TokenHeadersValues interface{} `json:"token_headers_values"`
TokenPostArgsClient interface{} `json:"token_post_args_client"`
TokenPostArgsNames interface{} `json:"token_post_args_names"`
TokenPostArgsValues interface{} `json:"token_post_args_values"`
UnauthorizedErrorMessage string `json:"unauthorized_error_message"`
UnauthorizedRedirectUri interface{} `json:"unauthorized_redirect_uri"`
UnexpectedRedirectUri interface{} `json:"unexpected_redirect_uri"`
UpstreamAccessTokenHeader string `json:"upstream_access_token_header"`
UpstreamAccessTokenJwkHeader interface{} `json:"upstream_access_token_jwk_header"`
UpstreamHeadersClaims interface{} `json:"upstream_headers_claims"`
UpstreamHeadersNames interface{} `json:"upstream_headers_names"`
UpstreamIdTokenHeader interface{} `json:"upstream_id_token_header"`
UpstreamIdTokenJwkHeader interface{} `json:"upstream_id_token_jwk_header"`
UpstreamIntrospectionHeader interface{} `json:"upstream_introspection_header"`
UpstreamRefreshTokenHeader interface{} `json:"upstream_refresh_token_header"`
UpstreamSessionIdHeader interface{} `json:"upstream_session_id_header"`
UpstreamUserInfoHeader interface{} `json:"upstream_user_info_header"`
UserinfoEndpoint interface{} `json:"userinfo_endpoint"`
VerifyClaims bool `json:"verify_claims"`
VerifyNonce bool `json:"verify_nonce"`
VerifyParameters bool `json:"verify_parameters"`
VerifySignature bool `json:"verify_signature"`
SessionRedis SessionRedis `json:"session_redis"`
}
type SessionRedis struct {
SessionRedisAuth interface{} `json:"session_redis_auth"`
SessionRedisClusterMaxredirections interface{} `json:"session_redis_cluster_maxredirections"`
SessionRedisClusterNodes interface{} `json:"session_redis_cluster_nodes"`
SessionRedisConnectTimeout interface{} `json:"session_redis_connect_timeout"`
SessionRedisHost string `json:"session_redis_host"`
SessionRedisPort int `json:"session_redis_port"`
SessionRedisPrefix string `json:"session_redis_prefix"`
SessionRedisReadTimeout interface{} `json:"session_redis_read_timeout"`
SessionRedisSendTimeout interface{} `json:"session_redis_send_timeout"`
SessionRedisServerName interface{} `json:"session_redis_server_name"`
SessionRedisSocket interface{} `json:"session_redis_socket"`
SessionRedisSsl bool `json:"session_redis_ssl"`
SessionRedisSslVerify bool `json:"session_redis_ssl_verify"`
}
type SessionCookie struct {
SessionCookieHttponly bool `json:"session_cookie_httponly"`
SessionCookieIdletime interface{} `json:"session_cookie_idletime"`
SessionCookieLifetime int `json:"session_cookie_lifetime"`
SessionCookieMaxsize int `json:"session_cookie_maxsize"`
SessionCookieName string `json:"session_cookie_name"`
SessionCookiePath string `json:"session_cookie_path"`
SessionCookieRenew int `json:"session_cookie_renew"`
SessionCookieSamesite string `json:"session_cookie_samesite"`
SessionCookieSecure interface{} `json:"session_cookie_secure"`
}
type Introspection struct {
IntrospectionEndpoint interface{} `json:"introspection_endpoint"`
IntrospectionEndpointAuthMethod interface{} `json:"introspection_endpoint_auth_method"`
IntrospectionHeadersClient interface{} `json:"introspection_headers_client"`
IntrospectionHeadersNames interface{} `json:"introspection_headers_names"`
IntrospectionHeadersValues interface{} `json:"introspection_headers_values"`
IntrospectionHint string `json:"introspection_hint"`
IntrospectionPostArgsClient interface{} `json:"introspection_post_args_client"`
IntrospectionPostArgsNames interface{} `json:"introspection_post_args_names"`
IntrospectionPostArgsValues interface{} `json:"introspection_post_args_values"`
}
type DownStream struct {
}
type Upstream struct {
DownstreamAccessTokenHeader interface{} `json:"downstream_access_token_header"`
DownstreamAccessTokenJwkHeader interface{} `json:"downstream_access_token_jwk_header"`
DownstreamHeadersClaims interface{} `json:"downstream_headers_claims"`
DownstreamHeadersNames interface{} `json:"downstream_headers_names"`
DownstreamIdTokenHeader interface{} `json:"downstream_id_token_header"`
DownstreamIdTokenJwkHeader interface{} `json:"downstream_id_token_jwk_header"`
DownstreamIntrospectionHeader interface{} `json:"downstream_introspection_header"`
DownstreamRefreshTokenHeader interface{} `json:"downstream_refresh_token_header"`
DownstreamSessionIdHeader interface{} `json:"downstream_session_id_header"`
DownstreamUserInfoHeader interface{} `json:"downstream_user_info_header"`
}

17
go.mod
View File

@@ -17,6 +17,7 @@ require (
github.com/hashicorp/consul/api v1.9.1
github.com/influxdata/influxdb-client-go/v2 v2.12.1
github.com/jhump/protoreflect v1.14.1
github.com/lestrrat-go/jwx v1.2.28
github.com/nacos-group/nacos-sdk-go/v2 v2.2.3
github.com/nsqio/go-nsq v1.1.0
github.com/ohler55/ojg v1.12.9
@@ -26,8 +27,8 @@ require (
github.com/soheilhy/cmux v0.1.5
github.com/urfave/cli/v2 v2.23.4
github.com/valyala/fasthttp v1.47.0
golang.org/x/crypto v0.7.0
golang.org/x/net v0.8.0
golang.org/x/crypto v0.17.0
golang.org/x/net v0.10.0
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1
)
@@ -41,6 +42,7 @@ require (
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/creasty/defaults v1.5.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dubbogo/triple v1.1.8 // indirect
github.com/go-kit/log v0.1.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
@@ -50,6 +52,7 @@ require (
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
@@ -60,6 +63,11 @@ require (
github.com/k0kubun/pp v3.0.1+incompatible // indirect
github.com/knadh/koanf v1.4.1 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
@@ -141,7 +149,6 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
@@ -159,8 +166,8 @@ require (
go.uber.org/atomic v1.9.0
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.23.0
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0
golang.org/x/time v0.1.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect