You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by ti...@apache.org on 2019/12/13 08:29:45 UTC
[servicecomb-mesher] branch master updated: add oauth2
authorization module (#91)
This is an automated email from the ASF dual-hosted git repository.
tianxiaoliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-mesher.git
The following commit(s) were added to refs/heads/master by this push:
new b89c769 add oauth2 authorization module (#91)
b89c769 is described below
commit b89c7696bdac0df8bab00b0b14c15ebe6285fd23
Author: Cong Young <53...@users.noreply.github.com>
AuthorDate: Fri Dec 13 16:29:36 2019 +0800
add oauth2 authorization module (#91)
---
cmd/mesher/mesher.go | 2 +
docs/oauth2/oauth2.md | 54 +++++++++++
examples/edge/conf/chassis.yaml | 2 +-
go.mod | 1 +
proxy/handler/oauth2/api.go | 39 ++++++++
proxy/handler/oauth2/oauth2_handler.go | 137 ++++++++++++++++++++++++++++
proxy/handler/oauth2/oauth2_handler_test.go | 117 ++++++++++++++++++++++++
proxy/protocol/http/gateway.go | 1 +
8 files changed, 352 insertions(+), 1 deletion(-)
diff --git a/cmd/mesher/mesher.go b/cmd/mesher/mesher.go
index 17e9c31..d8c3369 100644
--- a/cmd/mesher/mesher.go
+++ b/cmd/mesher/mesher.go
@@ -39,6 +39,8 @@ import (
_ "github.com/apache/servicecomb-mesher/proxy/pkg/egress/pilot"
_ "github.com/apache/servicecomb-mesher/proxy/control/istio"
+
+ _ "github.com/apache/servicecomb-mesher/proxy/handler/oauth2"
)
func main() {
diff --git a/docs/oauth2/oauth2.md b/docs/oauth2/oauth2.md
new file mode 100644
index 0000000..0e82f02
--- /dev/null
+++ b/docs/oauth2/oauth2.md
@@ -0,0 +1,54 @@
+# OAuth2
+
+Mesher provides a high-level general-purpose middleware abstraction layer. One of the abstractions is oauth2, which is free user learning [complexity inside handler chain](https://docs.go-chassis.com/dev-guides/how-to-implement-handler.html), so that users only need to focus on the development of their own business.
+
+## configuration
+
+Writing business code
+
+When you use authorization code model, you need to implement the follow parameters. Otherwise you need to implement the interface of config in oauth2/api.go.
+
+For example, implement the authorization code model **In oauth2_handler.go**
+
+```go
+ Use(&OAuth2{
+ GrantType: "authorization_code", // Registration grand type
+ // The default is the authorization code model
+ Authenticate: func(accessToken string, req *http.Request) error {
+ // implement the function
+ return nil
+ },
+ UseConfig: &oauth2.Config{
+ ClientID: "", // (required, string) your client_ID
+ ClientSecret: "", // (required, string) your client_Secret
+ Scopes: []string{""}, // (optional, string) scope specifies requested permissions
+ RedirectURL: "", // (required, string) URL to redirect users going through the OAuth2 flow,
+ Endpoint: oauth2.Endpoint{ // (required, string) your auth server endpoint
+ AuthURL: "",
+ TokenURL: "",
+ },
+ },
+ })
+```
+
+Change the configuration file and add the oauth2 handler to the chain. Note that as authentication, generally speaking,it is a server function, it must be placed in the provider chain.
+
+```yaml
+handler:
+ chain:
+ Consumer:
+ outgoing:
+ Provider:
+ incoming: oauth2 #provider handlers
+```
+
+## How to use
+
+**oauth2-handler Init**
+- [1] Implement the interface definition in /oauth2/api.go.
+- [2] Adding oauth2's provider handler name oauth2 defined in /oauth2 to providerChain.
+- [3] You must import proxy/handler/oauth2 to init oauth2 handler. All the handlers which are customized for mesher are defined in file cmd/mesher/mesher.go.
+- more details about handler chains in [go-chassis](https://github.com/go-chassis/go-chassis#readme)
+
+
+
diff --git a/examples/edge/conf/chassis.yaml b/examples/edge/conf/chassis.yaml
index de8325b..214bcf6 100644
--- a/examples/edge/conf/chassis.yaml
+++ b/examples/edge/conf/chassis.yaml
@@ -20,7 +20,7 @@ cse:
Consumer:
outgoing: router,bizkeeper-consumer,loadbalance,tracing-consumer,transport #consumer handlers
Provider:
- incoming: tracing-provider #provider handlers
+ incoming: oauth2,tracing-provider #provider handlers
## Mesher TLS is base on Go Chassis TLS config, https://docs.go-chassis.com/user-guides/tls.html
ssl:
diff --git a/go.mod b/go.mod
index d21e1cb..00ab1a4 100644
--- a/go.mod
+++ b/go.mod
@@ -23,6 +23,7 @@ require (
github.com/tetratelabs/go2sky v0.1.1-0.20190703154722-1eaab8035277
github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a
golang.org/x/net v0.0.0-20190311183353-d8887717615a
+ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
google.golang.org/grpc v1.19.1
gopkg.in/inf.v0 v0.9.1 // indirect
diff --git a/proxy/handler/oauth2/api.go b/proxy/handler/oauth2/api.go
new file mode 100644
index 0000000..7c774a3
--- /dev/null
+++ b/proxy/handler/oauth2/api.go
@@ -0,0 +1,39 @@
+/*
+ * 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 oauth2
+
+import (
+ "golang.org/x/oauth2"
+ "net/http"
+)
+
+var auth *OAuth2
+
+// OAuth2 should implement oauth2 server side logic
+// it is singleton
+type OAuth2 struct {
+ GrantType string // required
+ UseConfig *oauth2.Config // required
+ Authenticate func(accessToken string, req *http.Request) error // optional
+}
+
+// Use put a custom oauth2 logic
+// then register handler to chassis
+func Use(middleware *OAuth2) {
+ auth = middleware
+}
diff --git a/proxy/handler/oauth2/oauth2_handler.go b/proxy/handler/oauth2/oauth2_handler.go
new file mode 100644
index 0000000..ed50d1e
--- /dev/null
+++ b/proxy/handler/oauth2/oauth2_handler.go
@@ -0,0 +1,137 @@
+/*
+ * 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 oauth2
+
+import (
+ "context"
+ "errors"
+ "github.com/go-chassis/go-chassis/core/handler"
+ "github.com/go-chassis/go-chassis/core/invocation"
+ "github.com/go-mesh/openlogging"
+ "net/http"
+ "time"
+)
+
+// errors
+var (
+ ErrNoGrandType = errors.New("no grant_type found")
+ ErrInvalidCode = errors.New("invalid code")
+ ErrInvalidToken = errors.New("invalid authorization")
+ ErrInvalidAuth = errors.New("invalid authentication")
+ ErrExpiredToken = errors.New("expired token")
+)
+
+// AuthName is a constant
+const AuthName = "oauth2"
+
+// Handler is is a oauth2 pre process raw data in handler
+type Handler struct {
+}
+
+// Handle is provider
+func (oa *Handler) Handle(chain *handler.Chain, inv *invocation.Invocation, cb invocation.ResponseCallBack) {
+ if req, ok := inv.Args.(*http.Request); ok {
+ grantType := req.FormValue("grant_type")
+ if grantType == "" {
+ WriteBackErr(ErrNoGrandType, http.StatusUnauthorized, cb)
+ return
+ }
+
+ if auth != nil && auth.GrantType == "authorization_code" {
+ if req, ok := inv.Args.(*http.Request); ok {
+ code := req.FormValue("code")
+ if code == "" {
+ WriteBackErr(ErrInvalidCode, http.StatusUnauthorized, cb)
+ return
+ }
+
+ accessToken, err := getToken(code, cb)
+ if err != nil {
+ openlogging.Error("authorization error: " + err.Error())
+ WriteBackErr(ErrInvalidToken, http.StatusUnauthorized, cb)
+ return
+ }
+
+ if auth.Authenticate != nil {
+ err = auth.Authenticate(accessToken, req)
+ if err != nil {
+ openlogging.Error("authentication error: " + err.Error())
+ WriteBackErr(ErrInvalidAuth, http.StatusUnauthorized, cb)
+ return
+ }
+ }
+ }
+ }
+ chain.Next(inv, func(r *invocation.Response) error {
+ return cb(r)
+ })
+ }
+}
+
+// getToken deal with the authorization code and return the token
+func getToken(code string, cb invocation.ResponseCallBack) (accessToken string, err error) {
+ if auth.UseConfig != nil {
+ config := auth.UseConfig
+ token, err := config.Exchange(context.Background(), code)
+ if err != nil {
+ openlogging.Error("get token failed, errors: " + err.Error())
+ WriteBackErr(ErrInvalidCode, http.StatusUnauthorized, cb)
+ return "", err
+ }
+
+ // set the expiry token in 30 minutes
+ token.Expiry = time.Now().Add(30 * 60 * time.Second)
+ if time.Now().After(token.Expiry) {
+ return "", ErrExpiredToken
+ }
+ accessToken = token.AccessToken
+ return accessToken, nil
+ }
+ return "", nil
+}
+
+// Name returns router string
+func (oa *Handler) Name() string {
+ return AuthName
+}
+
+// NewOAuth2 returns new auth handler
+func NewOAuth2() handler.Handler {
+ return &Handler{}
+}
+
+func init() {
+ err := handler.RegisterHandler(AuthName, NewOAuth2)
+ if err != nil {
+ openlogging.Error("register handler error: " + err.Error())
+ return
+ }
+}
+
+// WriteBackErr write err and callback
+func WriteBackErr(err error, status int, cb invocation.ResponseCallBack) {
+ r := &invocation.Response{
+ Err: err,
+ Status: status,
+ }
+ err = cb(r)
+ if err != nil {
+ openlogging.Error("response error: " + err.Error())
+ return
+ }
+}
diff --git a/proxy/handler/oauth2/oauth2_handler_test.go b/proxy/handler/oauth2/oauth2_handler_test.go
new file mode 100644
index 0000000..ba5df52
--- /dev/null
+++ b/proxy/handler/oauth2/oauth2_handler_test.go
@@ -0,0 +1,117 @@
+/*
+ * 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 oauth2
+
+import (
+ "github.com/go-chassis/go-chassis/core/config"
+ "github.com/go-chassis/go-chassis/core/config/model"
+ "github.com/go-chassis/go-chassis/core/handler"
+ "github.com/go-chassis/go-chassis/core/invocation"
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/oauth2"
+ "net/http"
+ "testing"
+)
+
+func initHandler() handler.Chain {
+ c := handler.Chain{}
+ c.AddHandler(&Handler{})
+ return c
+}
+
+func initInv() *invocation.Invocation {
+
+ config.GlobalDefinition = &model.GlobalCfg{}
+ config.GlobalDefinition.Cse.Handler.Chain.Provider = make(map[string]string)
+ config.GlobalDefinition.Cse.Handler.Chain.Provider["outgoing"] = AuthName
+
+ var i *invocation.Invocation
+
+ i = invocation.New(nil)
+ i.MicroServiceName = "service1"
+ i.SchemaID = "schema1"
+ i.OperationID = "SayHello"
+ i.Endpoint = ""
+
+ return i
+}
+func TestOAuth2_Handle(t *testing.T) {
+ c := initHandler()
+ i := initInv()
+
+ Use(&OAuth2{
+ GrantType: "authorization_code",
+ Authenticate: func(at string, req *http.Request) error {
+ return nil
+ },
+ UseConfig: &oauth2.Config{
+ ClientID: "", // (required, string) your client_ID
+ ClientSecret: "", // (required, string) your client_Secret
+ Scopes: []string{""}, // (optional, string) scope specifies requested permissions
+ RedirectURL: "", // (required, string) URL to redirect users going through the OAuth2 flow
+ Endpoint: oauth2.Endpoint{ // (required, string) your auth server endpoint
+ AuthURL: "",
+ TokenURL: "",
+ },
+ },
+ })
+
+ t.Run("Invalid grant_type", func(t *testing.T) {
+ req, err := http.NewRequest(http.MethodPost, "https://api/?grant_type=test&code=test", nil)
+ if err != nil {
+ t.Errorf("authorization failed: %s", err.Error())
+ return
+ }
+ i.Args = req
+
+ i.SetHeader("Authorization", "Basic dGVzdDp0ZXN0")
+ c.Next(i, func(r *invocation.Response) error {
+ assert.Error(t, r.Err)
+ return r.Err
+ })
+ })
+
+ t.Run("Normal grant_type", func(t *testing.T) {
+ req, err := http.NewRequest(http.MethodPost, "https://api/?grant_type=authorization_code&code=test", nil)
+ if err != nil {
+ t.Errorf("authorization failed: %s", err.Error())
+ return
+ }
+ i.Args = req
+
+ c.Next(i, func(r *invocation.Response) error {
+ assert.NoError(t, r.Err)
+ return r.Err
+ })
+ })
+
+ t.Run("Null grant_type", func(t *testing.T) {
+ req, err := http.NewRequest(http.MethodPost, "https://api/?grant_type=&code=test", nil)
+ if err != nil {
+ t.Errorf("authorization failed: %s", err.Error())
+ return
+ }
+ i.Args = req
+
+ i.SetHeader("Authorization", "Basic dGVzdDp0ZXN0")
+ c.Next(i, func(r *invocation.Response) error {
+ assert.NoError(t, r.Err)
+ return r.Err
+ })
+ })
+}
diff --git a/proxy/protocol/http/gateway.go b/proxy/protocol/http/gateway.go
index ff8f173..d89dd4d 100644
--- a/proxy/protocol/http/gateway.go
+++ b/proxy/protocol/http/gateway.go
@@ -50,6 +50,7 @@ func HandleIngressTraffic(w http.ResponseWriter, r *http.Request) {
inv := &invocation.Invocation{}
inv.Reply = rest.NewResponse()
inv.Protocol = "rest"
+ inv.Args = r
h := make(map[string]string)
for k := range r.Header {
h[k] = r.Header.Get(k)