diff --git a/common/constant/default.go b/common/constant/default.go index 541ed1d330..784ed5945c 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -40,7 +40,7 @@ const ( const ( DEFAULT_KEY = "default" PREFIX_DEFAULT_KEY = "default." - DEFAULT_SERVICE_FILTERS = "echo" + DEFAULT_SERVICE_FILTERS = "echo,token" DEFAULT_REFERENCE_FILTERS = "" GENERIC_REFERENCE_FILTERS = "generic" GENERIC = "$invoke" diff --git a/common/constant/key.go b/common/constant/key.go index ed60c24b63..ed1fea2fe8 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -31,6 +31,7 @@ const ( TIMEOUT_KEY = "timeout" BEAN_NAME_KEY = "bean.name" GENERIC_KEY = "generic" + TOKEN_KEY = "token" ) const ( diff --git a/common/url.go b/common/url.go index 7c3f7e056c..c7fcf3766e 100644 --- a/common/url.go +++ b/common/url.go @@ -32,6 +32,7 @@ import ( import ( perrors "github.com/pkg/errors" + "github.com/satori/go.uuid" ) import ( @@ -148,6 +149,18 @@ func WithLocation(location string) option { } } +func WithToken(token string) option { + return func(url *URL) { + if len(token) > 0 { + value := token + if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" { + value = uuid.NewV4().String() + } + url.SetParam(constant.TOKEN_KEY, value) + } + } +} + func NewURLWithOptions(opts ...option) *URL { url := &URL{} for _, opt := range opts { diff --git a/config/service_config.go b/config/service_config.go index 5f08681838..da40bf3534 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -56,6 +56,7 @@ type ServiceConfig struct { Warmup string `yaml:"warmup" json:"warmup,omitempty" property:"warmup"` Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` + Token string `yaml:"token" json:"token,omitempty" property:"token"` unexported *atomic.Bool exported *atomic.Bool rpcService common.RPCService @@ -111,7 +112,9 @@ func (srvconfig *ServiceConfig) Export() error { common.WithPort(proto.Port), common.WithParams(urlMap), common.WithParamsValue(constant.BEAN_NAME_KEY, srvconfig.id), - common.WithMethods(strings.Split(methods, ","))) + common.WithMethods(strings.Split(methods, ",")), + common.WithToken(srvconfig.Token), + ) if len(regUrls) > 0 { for _, regUrl := range regUrls { diff --git a/examples/helloworld/dubbo/go-server/profiles/dev/server.yml b/examples/helloworld/dubbo/go-server/profiles/dev/server.yml index 7df17934f6..c2de3fb476 100644 --- a/examples/helloworld/dubbo/go-server/profiles/dev/server.yml +++ b/examples/helloworld/dubbo/go-server/profiles/dev/server.yml @@ -30,6 +30,7 @@ services: - name: "GetUser" retries: "1" loadbalance: "random" + token: "true" protocols: "dubbo": diff --git a/filter/impl/token_filter.go b/filter/impl/token_filter.go new file mode 100644 index 0000000000..4a8aec3f2b --- /dev/null +++ b/filter/impl/token_filter.go @@ -0,0 +1,68 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package impl + +import ( + "strings" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +const ( + token = "token" +) + +func init() { + extension.SetFilter(token, GetTokenFilter) +} + +type TokenFilter struct{} + +func (tf *TokenFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + invokerTkn := invoker.GetUrl().GetParam(constant.TOKEN_KEY, "") + if len(invokerTkn) > 0 { + attachs := invocation.Attachments() + if len(attachs) > 0 { + remoteTkn, exist := attachs[constant.TOKEN_KEY] + if exist && strings.EqualFold(invokerTkn, remoteTkn) { + return invoker.Invoke(invocation) + } + } + return &protocol.RPCResult{Err: perrors.Errorf("Invalid token! Forbid invoke remote service %s method %s ", + invoker, invocation.MethodName())} + } + + return invoker.Invoke(invocation) +} + +func (tf *TokenFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + return result +} + +func GetTokenFilter() filter.Filter { + return &TokenFilter{} +} diff --git a/filter/impl/token_filter_test.go b/filter/impl/token_filter_test.go new file mode 100644 index 0000000000..1473f27403 --- /dev/null +++ b/filter/impl/token_filter_test.go @@ -0,0 +1,85 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package impl + +import ( + "net/url" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestTokenFilter_Invoke(t *testing.T) { + filter := GetTokenFilter() + + url := common.NewURLWithOptions( + common.WithParams(url.Values{}), + common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) + attch := make(map[string]string, 0) + attch[constant.TOKEN_KEY] = "ori_key" + result := filter.Invoke(protocol.NewBaseInvoker(*url), + invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + assert.Nil(t, result.Error()) + assert.Nil(t, result.Result()) +} + +func TestTokenFilter_InvokeEmptyToken(t *testing.T) { + filter := GetTokenFilter() + + url := common.URL{} + attch := make(map[string]string, 0) + attch[constant.TOKEN_KEY] = "ori_key" + result := filter.Invoke(protocol.NewBaseInvoker(url), + invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + assert.Nil(t, result.Error()) + assert.Nil(t, result.Result()) +} + +func TestTokenFilter_InvokeEmptyAttach(t *testing.T) { + filter := GetTokenFilter() + + url := common.NewURLWithOptions( + common.WithParams(url.Values{}), + common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) + attch := make(map[string]string, 0) + result := filter.Invoke(protocol.NewBaseInvoker(*url), + invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + assert.NotNil(t, result.Error()) +} + +func TestTokenFilter_InvokeNotEqual(t *testing.T) { + filter := GetTokenFilter() + + url := common.NewURLWithOptions( + common.WithParams(url.Values{}), + common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) + attch := make(map[string]string, 0) + attch[constant.TOKEN_KEY] = "err_key" + result := filter.Invoke(protocol.NewBaseInvoker(*url), + invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + assert.NotNil(t, result.Error()) +} diff --git a/go.mod b/go.mod index 2645590296..0e0f9d65f9 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v1.1.0 // indirect github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec + github.com/satori/go.uuid v1.2.0 github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/soheilhy/cmux v0.1.4 // indirect github.com/stretchr/testify v1.3.0 diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go index 4bfc1324cf..1b736b1289 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -36,16 +36,29 @@ import ( var Err_No_Reply = perrors.New("request need @response") +var ( + attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY} +) + type DubboInvoker struct { protocol.BaseInvoker client *Client + attachment map[string]string destroyLock sync.Mutex } func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker { + attachment := make(map[string]string, 0) + for _, k := range attachmentKey { + if v := url.GetParam(k, ""); len(v) > 0 { + attachment[k] = v + } + } + return &DubboInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: client, + attachment: attachment, } } @@ -57,6 +70,11 @@ func (di *DubboInvoker) Invoke(invocation protocol.Invocation) protocol.Result { ) inv := invocation.(*invocation_impl.RPCInvocation) + if len(di.attachment) > 0 { + for k, v := range di.attachment { + inv.SetAttachments(k, v) + } + } url := di.GetUrl() // async async, err := strconv.ParseBool(inv.AttachmentsByKey(constant.ASYNC_KEY, "false"))