diff --git a/application/app.go b/application/app.go index b7b1cacf..43525951 100644 --- a/application/app.go +++ b/application/app.go @@ -1,6 +1,7 @@ package application import ( + "github.com/eolinker/eosc/eocontext" "github.com/eolinker/eosc/utils/config" ) @@ -14,6 +15,7 @@ func init() { } type IApp interface { + Auth(ctx eocontext.EoContext) error } func CheckSkill(skill string) bool { diff --git a/application/auth/aksk/aksk.go b/application/auth/aksk/aksk.go new file mode 100644 index 00000000..f2552e29 --- /dev/null +++ b/application/auth/aksk/aksk.go @@ -0,0 +1,100 @@ +package aksk + +import ( + "errors" + "fmt" + "github.com/eolinker/eosc/utils/config" + "time" + + "github.com/eolinker/apinto/auth" + "github.com/eolinker/eosc" + http_service "github.com/eolinker/eosc/eocontext/http-context" +) + +//supportTypes 当前驱动支持的authorization type值 +var supportTypes = []string{ + "ak/sk", + "aksk", +} + +type aksk struct { + id string + hideCredential bool + users *akskUsers +} + +func (a *aksk) Id() string { + return a.id +} + +func (a *aksk) Start() error { + return nil +} + +func (a *aksk) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + c, ok := conf.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s", config.TypeNameOf((*Config)(nil)), config.TypeNameOf(conf)) + } + + a.hideCredential = c.HideCredentials + + a.users = &akskUsers{ + users: c.Users, + } + + return nil +} + +func (a *aksk) Stop() error { + return nil +} + +func (a *aksk) CheckSkill(skill string) bool { + return auth.CheckSkill(skill) +} + +func (a *aksk) Auth(context http_service.IHttpContext) error { + authorizationType := context.Request().Header().GetHeader(auth.AuthorizationType) + if authorizationType == "" { + return auth.ErrorInvalidType + } + err := auth.CheckAuthorizationType(supportTypes, authorizationType) + if err != nil { + return err + } + //解析Authorization字符串 + encType, ak, signHeaders, signature, err := parseAuthorization(context) + //判断配置中是否存在该ak + for _, user := range a.users.users { + if ak == user.AK { + switch encType { + case "SDK-HMAC-SHA256", "HMAC-SHA256": + { + //结合context内的信息与配置的sk生成新的签名,与context携带的签名进行对比 + toSign := buildToSign(context, encType, signHeaders) + s := hmaxBySHA256(user.SK, toSign) + if s == signature { + // 判断鉴权是否已过期 + if user.Expire != 0 && time.Now().Unix() > user.Expire { + return errors.New("[ak/sk_auth] authorization expired") + } + + //若隐藏证书信息 + if a.hideCredential { + context.Proxy().Header().DelHeader(auth.Authorization) + } + + //将label set进context + for k, v := range user.Labels { + context.SetLabel(k, v) + } + return nil + } + } + } + } + } + + return errors.New("[ak/sk_auth] Invalid authorization") +} diff --git a/application/auth/aksk/aksk_test.go b/application/auth/aksk/aksk_test.go new file mode 100644 index 00000000..c4618644 --- /dev/null +++ b/application/auth/aksk/aksk_test.go @@ -0,0 +1,153 @@ +package aksk + +import ( + "io" + "net/http" + "testing" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/valyala/fasthttp" + + http_context "github.com/eolinker/apinto/node/http-context" +) + +var akskConfig = []AKSKConfig{{ + AK: "4c897cfdfca60a59983adc2627942e7e", + SK: "6bb8eee91f88336dd95b88a66709f0a3286ce1abf73453acc4619bc142d64040", + Labels: map[string]string{}, + Expire: 1658740726, //2022-07-25 17:18:46 +}} + +var testContexts = make([]http_service.IHttpContext, 0, 10) + +func TestAKSK(t *testing.T) { + testAKSK := &aksk{ + id: "123", + hideCredential: true, + users: &akskUsers{users: akskConfig}, + } + + createTestContext() + + err := testAKSK.Auth(testContexts[0]) + //if err != nil { + // t.Errorf("测试1:预期是能够通过鉴权,结果是%s", err.Error()) + //} + + err = testAKSK.Auth(testContexts[1]) + if err == nil { + t.Errorf("测试2:预期是不能够通过鉴权%s:,结果是nil", err.Error()) + } + + err = testAKSK.Auth(testContexts[2]) + if err == nil { + t.Errorf("测试3:预期是不能够通过鉴权%s:,结果是nil", err.Error()) + } +} + +func createTestContext() { + //使用正确sk加密后的签名 + + // http-service + //request1, _ := http-service.NewRequest("GET", "http://www.demo.com/demo/login?parm1=value1&parm2=", &body{}) + //request1.IHeader.SetDriver("Authorization-Type", "ak/sk") + //request1.IHeader.SetDriver("Content-Type", "application/json") + //request1.IHeader.SetDriver("x-gateway-date", "20200605T104456Z") + //request1.IHeader.SetDriver("Authorization", "HMAC-SHA256 Access=4c897cfdfca60a59983adc2627942e7e, SignedHeaders=content-type;host;x-gateway-date, Signature=0c3d2598d931f36ca7d261d52dcd29f09d6573671bd593b7cbc55f73eb942758") + //Context1 := http_context.NewContext(request1, &writer{}) + + // fast http-service + request1 := fasthttp.AcquireRequest() + request1.SetRequestURI("http://www.demo.com/demo/login?parm1=value1&parm2=") + request1.Header.SetMethod(fasthttp.MethodGet) + request1.Header.Set("Authorization-Type", "ak/sk") + request1.Header.Set("Content-Type", "application/json") + request1.Header.Set("x-gateway-date", "20200605T104456Z") + request1.Header.Set("Authorization", "HMAC-SHA256 Access=4c897cfdfca60a59983adc2627942e7e, SignedHeaders=content-type;host;x-gateway-date, Signature=0c3d2598d931f36ca7d261d52dcd29f09d6573671bd593b7cbc55f73eb942758") + context1 := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + request1.CopyTo(&context1.Request) + + Context1 := http_context.NewContext(context1, 0) + + testContexts = append(testContexts, Context1) + + //使用错误sk加密后的签名 + + // http-service + //request2, _ := http-service.NewRequest("GET", "http://www.demo.com/demo/login?parm1=value1&parm2=", &body{}) + //request2.IHeader.SetDriver("Authorization-Type", "ak/sk") + //request2.IHeader.SetDriver("Content-Type", "application/json") + //request2.IHeader.SetDriver("x-gateway-date", "20200605T104456Z") + //request2.IHeader.SetDriver("Authorization", "HMAC-SHA256 Access=4c897cfdfca60a59983adc2627942e7e, SignedHeaders=content-type;host;x-gateway-date, Signature=bb18110ddf327a9c1222a551527896d59cb854ca9084078cfa3a6eb23de3ddb8") + //Context2 := http_context.NewContext(request2, &writer{}) + + // https + request2 := fasthttp.AcquireRequest() + request2.SetRequestURI("http://www.demo.com/demo/login?parm1=value1&parm2=") + request2.Header.SetMethod(fasthttp.MethodGet) + request2.Header.Set("Authorization-Type", "ak/sk") + request2.Header.Set("Content-Type", "application/json") + request2.Header.Set("x-gateway-date", "20200605T104456Z") + request2.Header.Set("Authorization", "HMAC-SHA256 Access=4c897cfdfca60a59983adc2627942e7e, SignedHeaders=content-type;host;x-gateway-date, Signature=bb18110ddf327a9c1222a551527896d59cb854ca9084078cfa3a6eb23de3ddb8") + context2 := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + request2.CopyTo(&context2.Request) + + Context2 := http_context.NewContext(context2, 0) + testContexts = append(testContexts, Context2) + + //传输了不存在的ak + // http-service + //request3, _ := http-service.NewRequest("GET", "http://www.demo.com/demo/login?parm1=value1&parm2=", &body{}) + //request3.IHeader.SetDriver("Authorization-Type", "ak/sk") + //request3.IHeader.SetDriver("Content-Type", "application/json") + //request3.IHeader.SetDriver("x-gateway-date", "20200605T104456Z") + //request3.IHeader.SetDriver("Authorization", "HMAC-SHA256 Access=dsaasdasda, SignedHeaders=content-type;host;x-gateway-date, Signature=0c3d2598d931f36ca7d261d52dcd29f09d6573671bd593b7cbc55f73eb942758") + //Context3 := http_context.NewContext(request3, &writer{}) + //testContexts = append(testContexts, Context3) + + // fast http-service + request3 := fasthttp.AcquireRequest() + request3.SetRequestURI("http://www.demo.com/demo/login?parm1=value1&parm2=") + request3.Header.SetMethod(fasthttp.MethodGet) + request3.Header.Set("Authorization-Type", "ak/sk") + request3.Header.Set("Content-Type", "application/json") + request3.Header.Set("x-gateway-date", "20200605T104456Z") + request3.Header.Set("Authorization", "HMAC-SHA256 Access=dsaasdasda, SignedHeaders=content-type;host;x-gateway-date, Signature=0c3d2598d931f36ca7d261d52dcd29f09d6573671bd593b7cbc55f73eb942758") + context3 := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + request3.CopyTo(&context3.Request) + Context3 := http_context.NewContext(context3, 0) + testContexts = append(testContexts, Context3) +} + +type body struct { +} + +func (b body) Read(p []byte) (n int, err error) { + return len(p), io.EOF +} + +type writer struct { +} + +func (w *writer) Header() http.Header { + header := http.Header{} + return header +} + +func (w *writer) Write(bytes []byte) (int, error) { + return len(bytes), nil +} + +func (w *writer) WriteHeader(statusCode int) { + return +} diff --git a/application/auth/aksk/config.go b/application/auth/aksk/config.go new file mode 100644 index 00000000..40ebc559 --- /dev/null +++ b/application/auth/aksk/config.go @@ -0,0 +1,17 @@ +package aksk + +type Config struct { + HideCredentials bool `json:"hide_credentials" label:"是否隐藏证书"` + Users []AKSKConfig `json:"user" label:"用户列表"` +} + +type akskUsers struct { + users []AKSKConfig +} + +type AKSKConfig struct { + AK string `json:"ak" label:"Access Key" nullable:"false"` + SK string `json:"sk" label:"Secret Access Key" nullable:"false"` + Labels map[string]string `json:"labels" label:"用户标签"` + Expire int64 `json:"expire" format:"date-time" label:"过期时间"` +} diff --git a/application/auth/aksk/driver.go b/application/auth/aksk/driver.go new file mode 100644 index 00000000..fb75677e --- /dev/null +++ b/application/auth/aksk/driver.go @@ -0,0 +1,38 @@ +package aksk + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "aksk" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type +} + +//ConfigType 返回aksk鉴权驱动配置的反射类型 +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +//Create 创建aksk鉴权驱动实例 +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) { + a := &aksk{ + id: id, + } + err := a.Reset(v, workers) + if err != nil { + return nil, err + } + return a, nil +} diff --git a/application/auth/aksk/factory.go b/application/auth/aksk/factory.go new file mode 100644 index 00000000..cc70c1fa --- /dev/null +++ b/application/auth/aksk/factory.go @@ -0,0 +1,42 @@ +package aksk + +import ( + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/utils/schema" + "reflect" +) + +var name = "auth_aksk" + +//Register 注册aksk鉴权驱动工厂 +func Register(register eosc.IExtenderDriverRegister) { + register.RegisterExtenderDriver(name, NewFactory()) +} + +type factory struct { +} + +func (f *factory) Render() interface{} { + render, err := schema.Generate(reflect.TypeOf((*Config)(nil)), nil) + if err != nil { + return nil + } + return render +} + +//NewFactory 创建aksk鉴权驱动工厂 +func NewFactory() eosc.IExtenderDriverFactory { + return &factory{} +} + +//Create 创建aksk鉴权驱动 +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]interface{}) (eosc.IExtenderDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + }, nil +} diff --git a/application/auth/aksk/util.go b/application/auth/aksk/util.go new file mode 100644 index 00000000..477d260f --- /dev/null +++ b/application/auth/aksk/util.go @@ -0,0 +1,124 @@ +package aksk + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "strings" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/eolinker/apinto/auth" +) + +const dateHeader = "x-gateway-date" + +//buildToSign 构建待加密的签名所需字符串 +func buildToSign(ctx http_service.IHttpContext, encType string, signedHeaders []string) string { + toSign := strings.Builder{} + toSign.WriteString(encType + "\n") + dh := ctx.Request().Header().GetHeader(dateHeader) + toSign.WriteString(dh + "\n") + + cr := buildHexCanonicalRequest(ctx, signedHeaders) + toSign.WriteString(strings.ToLower(cr)) + return toSign.String() +} + +//buildHexCanonicalRequest 构建规范消息头 +func buildHexCanonicalRequest(ctx http_service.IHttpContext, signedHeaders []string) string { + cr := strings.Builder{} + + cr.WriteString(strings.ToUpper(ctx.Request().Method()) + "\n") + cr.WriteString(buildPath(ctx.Request().URI().Path()) + "\n") + cr.WriteString(ctx.Request().URI().RawQuery() + "\n") + + for _, header := range signedHeaders { + if strings.ToLower(header) == "host" { + cr.WriteString(buildHeaders(header, ctx.Request().Header().Host()) + "\n") + continue + } + v := ctx.Request().Header().GetHeader(header) + cr.WriteString(buildHeaders(header, v) + "\n") + } + cr.WriteString("\n") + cr.WriteString(strings.Join(signedHeaders, ";") + "\n") + body, _ := ctx.Request().Body().RawBody() + cr.WriteString(hexEncode(body)) + + return hexEncode([]byte(cr.String())) +} + +func buildPath(path string) string { + return strings.TrimSuffix(path, "/") + "/" +} + +func buildHeaders(hk, hv string) string { + return fmt.Sprintf("%s:%s", hk, strings.TrimSpace(hv)) +} + +func hexEncode(data []byte) string { + sha := sha256.New() + sha.Write(data) + return hex.EncodeToString(sha.Sum(nil)) +} + +func hmaxBySHA256(secretKey, toSign string) string { + // 创建对应的sha256哈希加密算法 + hm := hmac.New(sha256.New, []byte(secretKey)) + //写入加密数据 + hm.Write([]byte(toSign)) + return hex.EncodeToString(hm.Sum(nil)) +} + +func parseAuthorization(ctx http_service.IHttpContext) (encType string, accessKey string, signHeaders []string, signature string, err error) { + authStr := ctx.Request().Header().GetHeader(auth.Authorization) + + infos := strings.Split(authStr, ",") + if len(infos) < 3 { + err = errors.New("[ak/sk_auth] error authorization") + return + } + encType, accessKey, err = parseAccessKey(infos[0]) + if err != nil { + return + } + signHeaders, err = parseSignHeaders(infos[1]) + if err != nil { + return + } + signature, err = parseSignature(infos[2]) + if err != nil { + return + } + return +} + +func parseAccessKey(info string) (string, string, error) { + info = strings.TrimSpace(info) + akInfos := strings.Split(info, " ") + encType := "" + accessKey := "" + if len(akInfos) < 1 { + return "", "", errors.New("[ak/sk_auth] error access key") + } else if len(akInfos) == 1 { + accessKey = strings.Replace(akInfos[0], "Access=", "", 1) + } else if len(akInfos) == 2 { + encType = akInfos[0] + accessKey = strings.Replace(akInfos[1], "Access=", "", 1) + } + return encType, accessKey, nil +} + +func parseSignHeaders(info string) ([]string, error) { + info = strings.Replace(strings.TrimSpace(info), "SignedHeaders=", "", 1) + headers := strings.Split(strings.ToLower(info), ";") + return headers, nil +} + +func parseSignature(info string) (string, error) { + info = strings.Replace(strings.TrimSpace(info), "Signature=", "", 1) + return info, nil +} diff --git a/application/auth/apikey/apikey.go b/application/auth/apikey/apikey.go new file mode 100644 index 00000000..efefbbb4 --- /dev/null +++ b/application/auth/apikey/apikey.go @@ -0,0 +1,173 @@ +package apikey + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/eolinker/eosc/utils/config" + "reflect" + "strings" + "time" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/eolinker/apinto/auth" + "github.com/eolinker/eosc" +) + +//supportTypes 当前驱动支持的authorization type值 +var supportTypes = []string{ + "apikey", + "apikey_auth", + "apikey-auth", + "apikeyauth", +} + +type apikey struct { + id string + hideCredential bool + users *apiKeyUsers +} + +//Auth 鉴权处理 +func (a *apikey) Auth(ctx http_service.IHttpContext) error { + authorizationType := ctx.Request().Header().GetHeader(auth.AuthorizationType) + if authorizationType == "" { + return auth.ErrorInvalidType + } + // 判断是否要鉴权要求 + err := auth.CheckAuthorizationType(supportTypes, authorizationType) + if err != nil { + return err + } + authorization, err := a.getAuthValue(ctx) + if err != nil { + return err + } + for _, user := range a.users.users { + if authorization == user.Apikey { + if user.Expire == 0 || time.Now().Unix() < user.Expire { + //将label set进context + for k, v := range user.Labels { + ctx.SetLabel(k, v) + } + + return nil + } + return auth.ErrorExpireUser + } + } + return auth.ErrorInvalidUser + +} + +//TOfData 获取数据的类型 +func TOfData(data interface{}) reflect.Kind { + value := reflect.ValueOf(data) + valueType := value.Kind() + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + return valueType +} + +//getAuthValue 获取Apikey值 +func (a *apikey) getAuthValue(ctx http_service.IHttpContext) (string, error) { + // 判断鉴权值是否在header + + if authorization := ctx.Proxy().Header().GetHeader(auth.Authorization); authorization != "" { + if a.hideCredential { + ctx.Proxy().Header().DelHeader(auth.Authorization) + } + return authorization, nil + } + + // 判断鉴权值是否在query + if authorization := ctx.Proxy().URI().GetQuery("Apikey"); authorization != "" { + if a.hideCredential { + ctx.Proxy().URI().DelQuery("Apikey") + + } + return authorization, nil + } + var authorization string + contentType := ctx.Request().Header().GetHeader("Content-Type") + if strings.Contains(contentType, "application/x-www-form-urlencoded") || strings.Contains(contentType, "multipart/form-data") { + formParams, err := ctx.Proxy().Body().BodyForm() + if err != nil { + return "", err + } + authorization = formParams.Get("Apikey") + if a.hideCredential { + delete(formParams, "Apikey") + ctx.Proxy().Body().SetForm(formParams) + } + } else if strings.Contains(contentType, "application/json") { + var body map[string]interface{} + rawBody, err := ctx.Proxy().Body().RawBody() + if err != nil { + return "", err + } + if err = json.Unmarshal(rawBody, &body); err != nil { + return "", err + } + if _, ok := body["Apikey"]; !ok { + return "", errors.New("[apikey_auth] cannot find the Apikey in body") + } + if TOfData(body["Apikey"]) == reflect.String { + authorization = body["Apikey"].(string) + } else { + return "", errors.New("[apikey_auth] Invalid data type for Apikey") + } + + if a.hideCredential { + delete(body, "Apikey") + newBody, err := json.Marshal(body) + if err != nil { + return "", err + } + ctx.Proxy().Body().SetRaw(contentType, newBody) + } + + } else { + return "", errors.New("[apikey_auth] Unsupported Content-Type") + } + + if authorization != "" { + return authorization, nil + } + return "", errors.New("[apikey_auth] cannot find the Apikey in query/body/header") +} + +//Id 返回 worker ID +func (a *apikey) Id() string { + return a.id +} + +//Start +func (a *apikey) Start() error { + return nil +} + +//Reset 重新加载配置 +func (a *apikey) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + cfg, ok := conf.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s", config.TypeNameOf((*Config)(nil)), config.TypeNameOf(conf)) + } + a.users = &apiKeyUsers{ + users: cfg.User, + } + a.hideCredential = cfg.HideCredentials + return nil +} + +//Stop +func (a *apikey) Stop() error { + return nil +} + +//CheckSkill 技能检查 +func (a *apikey) CheckSkill(skill string) bool { + return auth.CheckSkill(skill) +} diff --git a/application/auth/apikey/apikey_test.go b/application/auth/apikey/apikey_test.go new file mode 100644 index 00000000..c0db09f3 --- /dev/null +++ b/application/auth/apikey/apikey_test.go @@ -0,0 +1,337 @@ +package apikey + +import ( + "bytes" + "mime/multipart" + + //"bytes" + "encoding/json" + "errors" + + "github.com/valyala/fasthttp" + + //"mime/multipart" + "net/http" + "net/url" + "strings" + "testing" + + "github.com/eolinker/apinto/auth" + http_context "github.com/eolinker/apinto/node/http-context" +) + +var ( + users = []User{ + { + Apikey: "asdqer", + Labels: make(map[string]string), + }, + { + Apikey: "eolinker", + Labels: make(map[string]string), + Expire: 0, + }, + { + Apikey: "apinto", + Labels: make(map[string]string), + Expire: 1627013522, + }, + } + cfg = &Config{ + User: users, + } +) + +func TestHeaderAuthorization(t *testing.T) { + worker, err := getWorker("", "AuthorizationType") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "Apikey", + "authorization": "eolinker", + } + // http-service + //req, err := buildRequest(headers, nil, "") + //if err != nil { + // t.Error(err) + // return + //} + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + req, err := buildFastRequest(headers, nil, "") + if err != nil { + t.Error(err) + return + } + err = worker.Auth(http_context.NewContext(req, 0)) + if err != nil { + t.Error(err) + return + } + t.Log("auth success") + return +} +func TestQueryAuthorization(t *testing.T) { + worker, err := getWorker("", "AuthorizationType") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "Apikey", + } + query := map[string]string{ + "Apikey": "eolinker", + } + // http-service + //req, err := buildRequest(headers, query, "") + //if err != nil { + // t.Error(err) + // return + //} + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + req, err := buildFastRequest(headers, query, "") + if err != nil { + t.Error(err) + return + } + err = worker.Auth(http_context.NewContext(req, 0)) + + if err != nil { + t.Error(err) + return + } + t.Log("auth success") + return +} +func TestBodyAuthorization(t *testing.T) { + var jsonBody = &struct { + Apikey string + }{ + Apikey: "eolinker", + } + + body, err := json.Marshal(jsonBody) + if err != nil { + t.Error(err) + return + } + worker, err := getWorker("", "AuthorizationType") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "Apikey", + "Content-Type": "application/json", + } + // http-service + //req, err := http-service.NewRequest(http-service.MethodPost, "localhost:8081", bytes.NewReader(body)) + //if err != nil { + // t.Error(err) + // return + //} + //for key, value := range headers { + // req.RequestHeader.SetDriver(key, value) + //} + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + req := fasthttp.AcquireRequest() + req.SetRequestURI("localhost:8081") + req.Header.SetMethod(fasthttp.MethodPost) + req.SetBody(body) + for key, value := range headers { + req.Header.Set(key, value) + } + context := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + req.CopyTo(&context.Request) + err = worker.Auth(http_context.NewContext(context, 0)) + if err != nil { + t.Error(err) + return + } + t.Log("auth success") + return +} + +func TestMultipartFormAuthorization(t *testing.T) { + buf := new(bytes.Buffer) + w := multipart.NewWriter(buf) + err := w.WriteField("Apikey", "eolinker") + if err != nil { + t.Error(err) + return + } + w.Close() + worker, err := getWorker("", "AuthorizationType") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "Apikey", + } + // http-service + //req, err := http-service.NewRequest(http-service.MethodPost, "localhost:8081", buf) + //if err != nil { + // t.Error(err) + // return + //} + //for key, value := range headers { + // req.RequestHeader.SetDriver(key, value) + //} + //req.RequestHeader.SetDriver("Content-Type", w.FormDataContentType()) + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + // fast http-service + req := fasthttp.AcquireRequest() + req.SetRequestURI("localhost:8081") + req.Header.SetMethod(fasthttp.MethodPost) + req.SetBodyString(buf.String()) + for key, value := range headers { + req.Header.Set(key, value) + } + req.Header.Set("Content-Type", w.FormDataContentType()) + context := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + req.CopyTo(&context.Request) + err = worker.Auth(http_context.NewContext(context, 0)) + if err != nil { + t.Error(err) + return + } + t.Log("auth success") + return +} +func TestFormAuthorization(t *testing.T) { + var formBody = url.Values{ + "Apikey": []string{"eolinker"}, + } + worker, err := getWorker("", "AuthorizationType") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "Apikey", + "Content-Type": "application/x-www-form-urlencoded", + } + // http-service + //req, err := buildRequest(headers, nil, formBody.encode()) + //if err != nil { + // t.Error(err) + // return + //} + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + req, err := buildFastRequest(headers, nil, formBody.Encode()) + if err != nil { + t.Error(err) + return + } + err = worker.Auth(http_context.NewContext(req, 0)) + + if err != nil { + t.Error(err) + return + } + t.Log("auth success") + return +} + +func getWorker(id string, name string) (auth.IAuth, error) { + f := NewFactory() + driver, err := f.Create("auth", "apikey", "", "apikey驱动", nil) + if err != nil { + return nil, err + } + worker, err := driver.Create(id, name, cfg, nil) + if err != nil { + return nil, err + } + a, ok := worker.(auth.IAuth) + if !ok { + return nil, errors.New("invalid struct type") + } + return a, nil +} + +func buildRequest(headers map[string]string, query map[string]string, body string) (*http.Request, error) { + method := http.MethodPost + if len(query) > 0 { + method = http.MethodGet + } + req, err := http.NewRequest(method, "localhost:8081", strings.NewReader(body)) + if err != nil { + return nil, err + } + for key, value := range headers { + req.Header.Set(key, value) + } + if len(query) > 0 { + params := make(url.Values) + for key, value := range query { + params.Add(key, value) + } + req.URL.RawQuery = params.Encode() + } + return req, err +} + +func buildFastRequest(headers map[string]string, query map[string]string, body string) (*fasthttp.RequestCtx, error) { + method := fasthttp.MethodPost + if len(query) > 0 { + method = fasthttp.MethodGet + } + req := fasthttp.AcquireRequest() + req.SetRequestURI("localhost:8081") + req.Header.SetMethod(method) + req.SetBodyString(body) + + for key, value := range headers { + req.Header.Set(key, value) + } + + if len(query) > 0 { + params := make(url.Values) + for key, value := range query { + params.Add(key, value) + } + req.URI().SetQueryString(params.Encode()) + } + + context := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + req.CopyTo(&context.Request) + return context, nil +} + +type writer struct { +} + +func (w writer) Header() http.Header { + panic("implement me") +} + +func (w writer) Write(bytes []byte) (int, error) { + panic("implement me") +} + +func (w writer) WriteHeader(statusCode int) { + panic("implement me") +} diff --git a/application/auth/apikey/config.go b/application/auth/apikey/config.go new file mode 100644 index 00000000..ff706543 --- /dev/null +++ b/application/auth/apikey/config.go @@ -0,0 +1,18 @@ +package apikey + +//Config apiKey配置内容 +type Config struct { + HideCredentials bool `json:"hide_credentials" label:"是否隐藏证书"` + User []User `json:"user" label:"用户列表"` +} + +type apiKeyUsers struct { + users []User +} + +//User 用户信息 +type User struct { + Apikey string `json:"apikey" label:"密钥(Apikey)" nullable:"false"` + Labels map[string]string `json:"labels" label:"用户标签"` + Expire int64 `json:"expire" format:"date-time" label:"过期时间"` +} diff --git a/application/auth/apikey/driver.go b/application/auth/apikey/driver.go new file mode 100644 index 00000000..56836808 --- /dev/null +++ b/application/auth/apikey/driver.go @@ -0,0 +1,38 @@ +package apikey + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "apikey" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) { + + w := &apikey{ + id: id, + } + err := w.Reset(v, workers) + if err != nil { + return nil, err + } + + return w, nil +} diff --git a/application/auth/apikey/factory.go b/application/auth/apikey/factory.go new file mode 100644 index 00000000..57909ff5 --- /dev/null +++ b/application/auth/apikey/factory.go @@ -0,0 +1,41 @@ +package apikey + +import ( + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/utils/schema" + "reflect" +) + +var name = "auth_apikey" + +//Register 注册auth驱动工厂 +func Register(register eosc.IExtenderDriverRegister) { + register.RegisterExtenderDriver(name, NewFactory()) +} + +type factory struct { +} + +//Create 创建apikey驱动 +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]interface{}) (eosc.IExtenderDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + }, nil +} +func (f *factory) Render() interface{} { + render, err := schema.Generate(reflect.TypeOf((*Config)(nil)), nil) + if err != nil { + return nil + } + return render +} + +//NewFactory 生成一个 auth_apiKey工厂 +func NewFactory() eosc.IExtenderDriverFactory { + return &factory{} +} diff --git a/application/auth/auth.go b/application/auth/auth.go new file mode 100644 index 00000000..829400f7 --- /dev/null +++ b/application/auth/auth.go @@ -0,0 +1,103 @@ +package auth + +import ( + "errors" + "fmt" + eoscContext "github.com/eolinker/eosc/eocontext" + "github.com/eolinker/eosc/log" + + "github.com/eolinker/eosc" +) + +var ( + ErrorInvalidAuth = errors.New("invalid auth") + defaultAuthFactoryRegister = newAuthFactoryManager() +) + +//IAuthFactory 鉴权工厂方法 +type IAuthFactory interface { + Create(driver string, config interface{}) (eoscContext.IAuthHandler, error) +} + +//IAuthFactoryRegister 实现了鉴权工厂管理器 +type IAuthFactoryRegister interface { + RegisterFactoryByKey(key string, factory IAuthFactory) + GetFactoryByKey(key string) (IAuthFactory, bool) + Keys() []string +} + +//driverRegister 驱动注册器 +type driverRegister struct { + register eosc.IRegister + keys []string +} + +//newAuthFactoryManager 创建auth工厂管理器 +func newAuthFactoryManager() IAuthFactoryRegister { + return &driverRegister{ + register: eosc.NewRegister(), + keys: make([]string, 0, 10), + } +} + +//GetFactoryByKey 获取指定auth工厂 +func (dm *driverRegister) GetFactoryByKey(key string) (IAuthFactory, bool) { + log.Debug("GetFactoryByKey:", key) + o, has := dm.register.Get(key) + if has { + log.Debug("GetFactoryByKey:", key, ":has") + f, ok := o.(IAuthFactory) + return f, ok + } + return nil, false +} + +//RegisterFactoryByKey 注册auth工厂 +func (dm *driverRegister) RegisterFactoryByKey(key string, factory IAuthFactory) { + err := dm.register.Register(key, factory, true) + log.Debug("RegisterFactoryByKey:", key) + + if err != nil { + log.Debug("RegisterFactoryByKey:", key, ":", err) + return + } + dm.keys = append(dm.keys, key) +} + +//Keys 返回所有已注册的key +func (dm *driverRegister) Keys() []string { + return dm.keys +} + +//Register 注册auth工厂到默认auth工厂注册器 +func Register(key string, factory IAuthFactory) { + + defaultAuthFactoryRegister.RegisterFactoryByKey(key, factory) +} + +//Get 从默认auth工厂注册器中获取auth工厂 +func Get(key string) (IAuthFactory, bool) { + return defaultAuthFactoryRegister.GetFactoryByKey(key) +} + +//Keys 返回默认的auth工厂注册器中所有已注册的key +func Keys() []string { + return defaultAuthFactoryRegister.Keys() +} + +//GetFactory 获取指定auth工厂,若指定的不存在则返回一个已注册的工厂 +func GetFactory(name string) (IAuthFactory, error) { + factory, ok := Get(name) + if !ok { + for _, key := range Keys() { + factory, ok = Get(key) + if ok { + break + } + } + if factory == nil { + return nil, fmt.Errorf("%s:%w", name, ErrorInvalidAuth) + } + } + return factory, nil +} diff --git a/application/auth/basic/basic.go b/application/auth/basic/basic.go new file mode 100644 index 00000000..7f193046 --- /dev/null +++ b/application/auth/basic/basic.go @@ -0,0 +1,125 @@ +package basic + +import ( + "encoding/base64" + "errors" + "fmt" + "github.com/eolinker/eosc/utils/config" + "strings" + "time" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/eolinker/eosc" + + "github.com/eolinker/apinto/auth" +) + +//supportTypes 当前驱动支持的authorization type值 +var supportTypes = []string{ + "basic", + "basic_auth", + "basic-auth", + "basicauth", +} + +type basic struct { + id string + hideCredential bool + users *basicUsers +} + +type basicUsers struct { + users []User +} + +func (b *basicUsers) check(ctx http_service.IHttpContext, username string, password string) error { + for _, u := range b.users { + if u.Username == username && u.Password == password { + if u.Expire == 0 || time.Now().Unix() < u.Expire { + //将label set进context + for k, v := range u.Labels { + ctx.SetLabel(k, v) + } + + return nil + } + return auth.ErrorExpireUser + } + } + return auth.ErrorInvalidUser +} + +func (b *basic) Id() string { + return b.id +} + +func (b *basic) Start() error { + return nil +} + +func (b *basic) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + cfg, ok := conf.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s", config.TypeNameOf((*Config)(nil)), config.TypeNameOf(conf)) + } + b.users = &basicUsers{ + cfg.User, + } + b.hideCredential = cfg.HideCredentials + return nil +} + +func (b *basic) Stop() error { + return nil +} + +func (b *basic) CheckSkill(skill string) bool { + return auth.CheckSkill(skill) +} + +func (b *basic) Auth(ctx http_service.IHttpContext) error { + authorizationType := ctx.Request().Header().GetHeader(auth.AuthorizationType) + if authorizationType == "" { + return auth.ErrorInvalidType + } + err := auth.CheckAuthorizationType(supportTypes, authorizationType) + if err != nil { + return err + } + authorization := ctx.Request().Header().GetHeader(auth.Authorization) + if b.hideCredential { + ctx.Proxy().Header().DelHeader(auth.Authorization) + } + + username, password, err := retrieveCredentials(authorization) + if err != nil { + return err + } + return b.users.check(ctx, username, password) +} + +//retrieveCredentials 获取basicAuth认证信息 +func retrieveCredentials(authInfo string) (string, string, error) { + + if authInfo != "" { + const basic = "basic" + l := len(basic) + + if len(authInfo) > l+1 && strings.ToLower(authInfo[:l]) == basic { + b, err := base64.StdEncoding.DecodeString(authInfo[l+1:]) + if err != nil { + return "", "", err + } + cred := string(b) + for i := 0; i < len(cred); i++ { + if cred[i] == ':' { + return cred[:i], cred[i+1:], nil + } + } + return "", "", errors.New("[basic_auth] header has unrecognized format") + } + return "", "", errors.New("[basic_auth] header has unrecognized format") + } + return "", "", errors.New("[basic_auth] authorization required") +} diff --git a/application/auth/basic/basic_test.go b/application/auth/basic/basic_test.go new file mode 100644 index 00000000..625f705b --- /dev/null +++ b/application/auth/basic/basic_test.go @@ -0,0 +1,224 @@ +package basic + +import ( + "errors" + "net/http" + "strings" + "testing" + + "github.com/valyala/fasthttp" + + "github.com/eolinker/apinto/auth" + + http_context "github.com/eolinker/apinto/node/http-context" +) + +var ( + users = []User{ + { + Username: "wu", + Password: "123456", + Expire: 1627009923, + }, + { + Username: "liu", + Password: "123456", + }, + { + Username: "chen", + Password: "123456", + Expire: 1627013522, + }, + } + cfg = &Config{ + HideCredentials: true, + User: users, + } +) + +func getWorker(id string, name string) (auth.IAuth, error) { + f := NewFactory() + driver, err := f.Create("auth", "basic", "", "basic驱动", nil) + if err != nil { + return nil, err + } + worker, err := driver.Create(id, name, cfg, nil) + if err != nil { + return nil, err + } + a, ok := worker.(auth.IAuth) + if !ok { + return nil, errors.New("invalid struct type") + } + return a, nil +} + +func TestSuccessAuthorization(t *testing.T) { + worker, err := getWorker("", "successAuthorization") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "basic", + "authorization": "Basic bGl1OjEyMzQ1Ng==", + } + // http-service + //req, err := buildRequest(headers) + //err = worker.Auth(http_context.NewContext(req, &writer{})) + //if err != nil { + // t.Error(err) + // return + //} + + // fast http-service + req, err := buildFastRequest(headers) + if err != nil { + t.Error(err) + return + } + err = worker.Auth(http_context.NewContext(req, 0)) + if err != nil { + t.Error(err) + return + } + + t.Log("auth success") + return +} + +func TestExpireAuthorization(t *testing.T) { + worker, err := getWorker("", "expireAuthorization") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "basic", + "authorization": "Basic d3U6MTIzNDU2", + } + // http-service + //req, err := buildRequest(headers) + //if err != nil { + // t.Error(err) + // return + //} + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + req, err := buildFastRequest(headers) + if err != nil { + t.Error(err) + return + } + err = worker.Auth(http_context.NewContext(req, 0)) + + if err == auth.ErrorExpireUser { + t.Log("success") + return + } + t.Error(err) + return +} + +func TestNoAuthorization(t *testing.T) { + worker, err := getWorker("", "noAuthorization") + if err != nil { + t.Error(err) + return + } + headers := map[string]string{ + "authorization-type": "basic", + } + // http-service + //req, err := buildRequest(headers) + //if err != nil { + // t.Error(err) + // return + //} + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + req, err := buildFastRequest(headers) + if err != nil { + t.Error(err) + return + } + err = worker.Auth(http_context.NewContext(req, 0)) + if err.Error() == "[basic_auth] authorization required" { + t.Log("success") + return + } + t.Error(err) + return +} + +func TestNoAuthorizationType(t *testing.T) { + worker, err := getWorker("", "noAuthorizationType") + if err != nil { + t.Error(err) + return + } + // http-service + //req, err := buildRequest(nil) + //if err != nil { + // t.Error(err) + // return + //} + //err = worker.Auth(http_context.NewContext(req, &writer{})) + + // fast http-service + headers := map[string]string{} + req, err := buildFastRequest(headers) + if err != nil { + t.Error(err) + return + } + err = worker.Auth(http_context.NewContext(req, 0)) + if err == auth.ErrorInvalidType { + t.Log("success") + return + } + t.Error(err) + return +} + +func buildRequest(headers map[string]string) (*http.Request, error) { + req, err := http.NewRequest("POST", "localhost:8081", strings.NewReader("")) + if err != nil { + return nil, err + } + for key, value := range headers { + req.Header.Set(key, value) + } + return req, err +} + +func buildFastRequest(headers map[string]string) (*fasthttp.RequestCtx, error) { + context := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + context.Request.Header.SetMethod(fasthttp.MethodPost) + for key, value := range headers { + context.Request.Header.Set(key, value) + } + context.Request.SetRequestURI("localhost:8081") + return context, nil +} + +type writer struct { +} + +func (w writer) Header() http.Header { + header := http.Header{} + return header +} + +func (w writer) Write(bytes []byte) (int, error) { + return len(bytes), nil +} + +func (w writer) WriteHeader(statusCode int) { + return +} diff --git a/application/auth/basic/config.go b/application/auth/basic/config.go new file mode 100644 index 00000000..7da1b068 --- /dev/null +++ b/application/auth/basic/config.go @@ -0,0 +1,15 @@ +package basic + +//Config basic配置内容 +type Config struct { + HideCredentials bool `json:"hide_credentials" label:"是否隐藏证书"` + User []User `json:"user" label:"用户列表"` +} + +//User 用户信息 +type User struct { + Username string `json:"username" label:"用户名(username)" nullable:"false"` + Password string `json:"password" label:"密码(password)" nullable:"false"` + Labels map[string]string `json:"labels" label:"用户标签"` + Expire int64 `json:"expire" format:"date-time" label:"过期时间"` +} diff --git a/application/auth/basic/driver.go b/application/auth/basic/driver.go new file mode 100644 index 00000000..066fef31 --- /dev/null +++ b/application/auth/basic/driver.go @@ -0,0 +1,40 @@ +package basic + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "basic" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + + label string + desc string + configType reflect.Type +} + +//ConfigType 返回basic驱动配置的反射类型 +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +//Create 创建http_proxy驱动的实例 +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) { + + w := &basic{ + id: id, + } + err := w.Reset(v, workers) + if err != nil { + return nil, err + } + + return w, nil +} diff --git a/application/auth/basic/factory.go b/application/auth/basic/factory.go new file mode 100644 index 00000000..06508ac0 --- /dev/null +++ b/application/auth/basic/factory.go @@ -0,0 +1,40 @@ +package basic + +import ( + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/utils/schema" + "reflect" +) + +var name = "auth_basic" + +//Register 注册http_proxy驱动工厂 +func Register(register eosc.IExtenderDriverRegister) { + register.RegisterExtenderDriver(name, NewFactory()) +} + +type factory struct { +} + +//NewFactory 创建http_proxy驱动工厂 +func NewFactory() eosc.IExtenderDriverFactory { + return &factory{} +} +func (f *factory) Render() interface{} { + render, err := schema.Generate(reflect.TypeOf((*Config)(nil)), nil) + if err != nil { + return nil + } + return render +} + +//Create 创建http_proxy驱动 +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]interface{}) (eosc.IExtenderDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + configType: reflect.TypeOf((*Config)(nil)), + }, nil +} diff --git a/application/auth/jwt/config.go b/application/auth/jwt/config.go new file mode 100644 index 00000000..c1665c9d --- /dev/null +++ b/application/auth/jwt/config.go @@ -0,0 +1,22 @@ +package jwt + +//Config JWT实例配置 +type Config struct { + Credentials []JwtCredential `json:"credentials" label:"证书列表"` + SignatureIsBase64 bool `json:"signature_is_base64" label:"base64加密"` + ClaimsToVerify []string `json:"claims_to_verify" label:"校验字段"` + HideCredentials bool `json:"hide_credentials" label:"是否隐藏证书"` +} + +type jwtUsers struct { + credentials []JwtCredential +} + +//JwtCredential JWT验证信息 +type JwtCredential struct { + Iss string `json:"iss" label:"证书签发者" description:"playload计算内容之一"` + Secret string `json:"secret" label:"密钥" description:"加密算法是HS时必填,用于校验token"` + RSAPublicKey string `json:"rsa_public_key" label:"RSA公钥" description:"加密算法是RS或ES时必填,用于校验token"` + Algorithm string `json:"algorithm" enum:"HS256,HS384,HS512,RS256,RS384,RS512,ES256,ES384,ES512" label:"签名算法"` + Labels map[string]string `json:"labels" label:"用户标签"` +} diff --git a/application/auth/jwt/driver.go b/application/auth/jwt/driver.go new file mode 100644 index 00000000..7968d429 --- /dev/null +++ b/application/auth/jwt/driver.go @@ -0,0 +1,38 @@ +package jwt + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "jwt" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type +} + +//ConfigType 返回jwt鉴权驱动配置的反射类型 +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +//Create 创建jwt鉴权驱动实例 +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) { + a := &jwt{ + id: id, + } + err := a.Reset(v, workers) + if err != nil { + return nil, err + } + return a, nil +} diff --git a/application/auth/jwt/factory.go b/application/auth/jwt/factory.go new file mode 100644 index 00000000..80d87086 --- /dev/null +++ b/application/auth/jwt/factory.go @@ -0,0 +1,43 @@ +package jwt + +import ( + "github.com/eolinker/eosc/utils/schema" + "reflect" + + "github.com/eolinker/eosc" +) + +var name = "auth_jwt" + +//Register 注册jwt鉴权驱动工厂 +func Register(register eosc.IExtenderDriverRegister) { + register.RegisterExtenderDriver(name, NewFactory()) +} + +type factory struct { +} + +func (f *factory) Render() interface{} { + render, err := schema.Generate(reflect.TypeOf((*Config)(nil)), nil) + if err != nil { + return nil + } + return render +} + +//NewFactory 创建jwt鉴权驱动工厂 +func NewFactory() eosc.IExtenderDriverFactory { + return &factory{} +} + +//Create 创建jwt鉴权驱动 +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]interface{}) (eosc.IExtenderDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + }, nil +} diff --git a/application/auth/jwt/jwt.go b/application/auth/jwt/jwt.go new file mode 100644 index 00000000..917f1061 --- /dev/null +++ b/application/auth/jwt/jwt.go @@ -0,0 +1,77 @@ +package jwt + +import ( + "fmt" + "github.com/eolinker/eosc/utils/config" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/eolinker/apinto/auth" + "github.com/eolinker/eosc" +) + +var _ auth.IAuth = (*jwt)(nil) + +//supportTypes 当前驱动支持的authorization type值 +var supportTypes = []string{ + "jwt", +} + +type jwt struct { + id string + credentials *jwtUsers + signatureIsBase64 bool + claimsToVerify []string + hideCredentials bool +} + +func (j *jwt) Id() string { + return j.id +} + +func (j *jwt) Start() error { + return nil +} + +func (j *jwt) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + c, ok := conf.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s", config.TypeNameOf((*Config)(nil)), config.TypeNameOf(conf)) + } + + j.credentials = &jwtUsers{ + credentials: c.Credentials, + } + + j.signatureIsBase64 = c.SignatureIsBase64 + j.claimsToVerify = c.ClaimsToVerify + j.hideCredentials = c.HideCredentials + + return nil +} + +func (j *jwt) Stop() error { + return nil +} + +func (j *jwt) CheckSkill(skill string) bool { + return auth.CheckSkill(skill) +} + +func (j *jwt) Auth(context http_service.IHttpContext) error { + authorizationType := context.Request().Header().GetHeader(auth.AuthorizationType) + if authorizationType == "" { + return auth.ErrorInvalidType + } + err := auth.CheckAuthorizationType(supportTypes, authorizationType) + if err != nil { + return err + } + + err = j.doJWTAuthentication(context) + if err != nil { + return err + } + + return nil +} diff --git a/application/auth/jwt/jwt_test.go b/application/auth/jwt/jwt_test.go new file mode 100644 index 00000000..dfcc43fb --- /dev/null +++ b/application/auth/jwt/jwt_test.go @@ -0,0 +1,158 @@ +package jwt + +import ( + "io" + "net/http" + "testing" + + "github.com/valyala/fasthttp" + + http_context "github.com/eolinker/apinto/node/http-context" +) + +type responseWriter struct{} + +func (w responseWriter) Header() http.Header { + return http.Header(map[string][]string{}) +} +func (w responseWriter) Write([]byte) (int, error) { + return 0, nil +} +func (w responseWriter) WriteHeader(statusCode int) { +} + +type requestBody struct{} + +func (r requestBody) Read(p []byte) (n int, err error) { + return 0, io.EOF +} + +type JWTTest struct { + testName string + token string + want string +} + +var JWTConf = &Config{ + SignatureIsBase64: false, + ClaimsToVerify: []string{"exp", "nbf"}, + HideCredentials: true, + Credentials: []JwtCredential{ + {Iss: "TestHS256", Secret: "eolinker", RSAPublicKey: "", Algorithm: "HS256"}, + {Iss: "TestHS384", Secret: "eolinker", RSAPublicKey: "", Algorithm: "HS384"}, + {Iss: "TestHS512", Secret: "eolinker", RSAPublicKey: "", Algorithm: "HS512"}, + {Iss: "TestRS256", Secret: "eolinker", RSAPublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\nvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc\naT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy\ntvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0\ne+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb\nV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9\nMwIDAQAB\n-----END PUBLIC KEY-----", Algorithm: "RS256"}, + {Iss: "TestRS384", Secret: "eolinker", RSAPublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\nvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc\naT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy\ntvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0\ne+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb\nV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9\nMwIDAQAB\n-----END PUBLIC KEY-----", Algorithm: "RS384"}, + {Iss: "TestRS512", Secret: "eolinker", RSAPublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\nvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc\naT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy\ntvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0\ne+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb\nV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9\nMwIDAQAB\n-----END PUBLIC KEY-----", Algorithm: "RS512"}, + {Iss: "TestES256", Secret: "eolinker", RSAPublicKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----", Algorithm: "ES256"}, + {Iss: "TestES384", Secret: "eolinker", RSAPublicKey: "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+\nPk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii\n1D3jaW6pmGVJFhodzC31cy5sfOYotrzF\n-----END PUBLIC KEY-----", Algorithm: "ES384"}, + {Iss: "TestES512", Secret: "eolinker", RSAPublicKey: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\nPDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\nAl8G7CqwoJOsW7Kddns=\n-----END PUBLIC KEY-----", Algorithm: "ES512"}, + }, +} + +var tests = []JWTTest{ + { + testName: "测试HS256", + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJUZXN0SFMyNTYiLCJuYmYiOjE2MjcyODkxNzUsImV4cCI6MTY1ODgyNTE3NX0.mOianLD1sBOVhJ9UZyrcQZgrBBBu9keviDRWydT6BXk", + want: "nil", + }, + { + testName: "测试HS384", + token: "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJUZXN0SFMzODQiLCJuYmYiOjE2MjcyODkxNzUsImV4cCI6MTY1ODgyNTE3NX0.K5xuz653Vz3fu4m6BnUFUl6Me1H-fHDS5WxG4yyz8ZlgJnzvaSz9-rGnBdE0XYep", + want: "nil", + }, + { + testName: "测试HS512", + token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJUZXN0SFM1MTIiLCJuYmYiOjE2MjcyODkxNzUsImV4cCI6MTY1ODgyNTE3NX0.G7JqIKxEZOR6LzuUb_Epphgs9FINBokziQvdKg3CmkY8-QtGLk2YpMkCpsiRW18OTPYZq19iCjpYXQCYA1O3nw", + want: "nil", + }, + { + testName: "测试RS256", + token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoiVGVzdFJTMjU2IiwibmJmIjoxNjI3Mjg5MTc1LCJleHAiOjE2NTg4MjUxNzV9.HdIDSuzWhWqgI1mx4P1udieu77n3vysBC1xDT75ppafJjv7k-F9ihusIjqPNwtj82qVNtmoIgqfOJ4YHrOqmE3cRk1G99l7Tx93WtcHqsFEo5SuFElpt0dk4Yq3haSh65sR_LdqxP4-H7FvOaPrJ5Jrkq3G1WBMGrzblUA2WCUzCZ9ANhKsQjV8WE16Bh6I7oI759KCAvhtdsnpu4KERpuYbcrffaVfuYfzkjhrawqRjAFt7fA4hcq1b9srEqmXxUc9IQ0AYmUWn9yyvNgdXGa-GrioT4Up7CYpmp5lbnjwgLo3cH7kDh5iyj-evVmOsyLvE2Ha9ZC-iV7wThyyE6g", + want: "nil", + }, + { + testName: "测试RS384", + token: "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoiVGVzdFJTMzg0IiwibmJmIjoxNjI3Mjg5MTc1LCJleHAiOjE2NTg4MjUxNzV9.J15a16W4S-uLwWMJ9M1cpw055u82APrNX0XARf4od4bjJl3dLdmj797bYcdOMn13NLw3y6KAftY5iDrBjIBiirMg4X0bHCrc1FrenA9vfb7lzhWztiIdkrqv1zs-nV21JVgMhYkpwBF99BDdHNTuzLWyTob0s0z1VHcRkAFvEjttBmPnlLeX_NrSwho-hPolybycvFfBJaoZ1qjtmYwVYQMt-0TVnet_jxhbPCvSBzNl-6wM6cmD5PvSJXI_tvO8TtxHuV5KLh7p1IVgVVwTURGffzIo_KcdcofM_NnQkNIl6x3yuGNkB8pFAhMDDYVQjcW2zoTzdtLBqad-UQFfLA", + want: "nil", + }, + { + testName: "测试RS512", + token: "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoiVGVzdFJTNTEyIiwibmJmIjoxNjI3Mjg5MTc1LCJleHAiOjE2NTg4MjUxNzV9.IpkdhYcUlAZb_nAPDI8sFCkb07TQ4wZ8e-HIZvlmzdKszl02mEaJXt9OgusVS5hZ3vohQ6abpxBEu0B89MzsJIbanH6Rz3oxmA-R837ac_6dEPHHOVTHbUawQ3qfa0YCb5HXQ2X4ldu-cmxlUeWi2ZTDOQU4bT7GoBneC8X-ae7c5zt71juIUlg0JxU53YLN93POqpWcNneJIGoC9VyX0NKm55eNp96yfXXWXaOhXxOXaZyeW4ACZ6GFu5H9BKkiG_tZjzA5Pppg5YxpsIpKrdEoTUnPpmoBCUWEsMc1T6KyoHAjIAXAgiNxLe3Q5Fgsf-789RSpzk1VeWetJkwGCA", + want: "nil", + }, + { + testName: "测试ES256", + token: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoiVGVzdEVTMjU2IiwibmJmIjoxNjI3Mjg5MTc1LCJleHAiOjE2NTg4MjUxNzV9.4XAE1wCY_5P7qGDOq04DfJWzgRJl28W1fN-Jh7a5sXj9ylYwRh5J35yoPz0lCfXAgt_hT_UjCAlB2h9OALKUGQ", + want: "nil", + }, + { + testName: "测试ES384", + token: "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6ImlUcVhYSTB6YkFuSkNLRGFvYmZoa00xZi02ck1TcFRmeVpNUnBfMnRLSTgifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoiVGVzdEVTMzg0IiwibmJmIjoxNjI3Mjg5MTc1LCJleHAiOjE2NTg4MjUxNzV9.Mt0ZXHkDM50vx1nEjJs2Ok1HnE-64kFAp4j_xGNbL0q9wWq2u6n1AhbcDGa0v6GuqWuV1vrMStUR1t-5OhYJeQxyMHNjLqxyD6GJ8THODXUDIy81Nt1zN-qpjj6H7JeC", + want: "nil", + }, + { + testName: "测试ES512", + token: "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoiVGVzdEVTNTEyIiwibmJmIjoxNjI3Mjg5MTc1LCJleHAiOjE2NTg4MjUxNzV9.AZZdNIQ_bhhMCRcjoz4exemQ2yXvtRgKp27Oc3JbrGNSTOYbxBQpHJ79zfRjdGIM6Un6cgAeJgwTsYberuun-ccDACiiWgKbER3qVssz5bG4rb7eiEHhTaRF5b0gqaDdQE3i3QWdJMxp15-QrJbOQawr2DV4stUAjiJkEFzCej3asEvd", + want: "nil", + }, + { + testName: "测试HS512过期的token", + token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJUZXN0SFM1MTIiLCJuYmYiOjE2MjcyODkxNzUsImV4cCI6MTYyNzI4OTIzNX0.5oUtf-royNU8ckXgaDDVCv5dpVVdFyL7fOVz57LaMHXlzRyPQfLNGLMeaIWY6ZUpqRbzT_Jm3OZMXs5MVRmZlw", + want: "错误", + }, + { + testName: "测试HS512未到开始时间nbf的token", + token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJUZXN0SFM1MTIiLCJuYmYiOjE2MzcyODkxNzUsImV4cCI6MTYzNzI4OTIzNX0.mHTikcPyIHUAVBaHBHHTmpORcZQrZOqFWJPBR9by4mvVpJ73_WNchd093_yDs61Eh0LHs44ww047k-S4s5KrVA", + want: "错误", + }, + { + testName: "测试HS256ISS不存在的token", + token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJUZXN0SFN4eHgiLCJuYmYiOjE2MjcyODkyMzUsImV4cCI6MTY1ODgyNTIzNX0.42_GWGNLlHG1K-bCrB-NI0fNLEa9F6LBcX-pfdQWUn1RE0nB0SewDI_PyA62NtnMOc_R5rMBHcwdW5WUCxiPVw", + want: "错误", + }, +} + +func TestJWT(t *testing.T) { + jwtMoudule := &jwt{} + err := jwtMoudule.Reset(JWTConf, nil) + if err != nil { + t.Errorf("配置读取出错 err:%s", err) + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + // http-service + //自己造formData, query或者body内插入jwt_token + //httpRequest, _ := http-service.NewRequest("GET", "/asd/asd/asd?jwt_token="+test.token, requestBody{}) + //httpRequest.RequestHeader.SetDriver("Content-Type", "multipart/form-data") + //ctx := http_context.NewContext(httpRequest, responseWriter{}) + //ctx.RequestOrg.SetHeader("Authorization-Type", "Jwt") + + // fasthttp + context := &fasthttp.RequestCtx{ + Request: *fasthttp.AcquireRequest(), + Response: *fasthttp.AcquireResponse(), + } + context.Request.Header.SetMethod(fasthttp.MethodGet) + context.Request.Header.Set("Content-Type", "multipart/form-data") + context.Request.SetRequestURI("/asd/asd/asd?jwt_token=" + test.token) + ctx := http_context.NewContext(context, 0) + + err = jwtMoudule.Auth(ctx) + + resultErr := "" + if err != nil { + resultErr = "错误" + } else { + resultErr = "nil" + } + + if resultErr != test.want { + t.Errorf("test %s Fail", test.testName) + } + + }) + } + +} diff --git a/application/auth/jwt/verify.go b/application/auth/jwt/verify.go new file mode 100644 index 00000000..c9f51caa --- /dev/null +++ b/application/auth/jwt/verify.go @@ -0,0 +1,615 @@ +package jwt + +import ( + "crypto" + "crypto/ecdsa" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "math/big" + "reflect" + "strings" + "time" + + http_service "github.com/eolinker/eosc/eocontext/http-context" +) + +type jwtToken struct { + Token string + Header64 string + Claims64 string + Signature64 string + Header map[string]interface{} + Claims map[string]interface{} + Signature string +} + +type signingMethod struct { + Name string + Hash crypto.Hash + KeySize int + CurveBits int +} + +var ( + errInvalidKey = errors.New("key is invalid") + errInvalidKeyType = errors.New("key is of invalid type") + errHashUnavailable = errors.New("the requested hash function is unavailable") + errSignatureInvalid = errors.New("signature is invalid") + errInvalidSigningMethod = errors.New("signing method is invalid") + errECDSAVerification = errors.New("crypto/ecdsa: verification error") + errKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") + errNotRSAPublicKey = errors.New("Key is not a valid RSA public key") + errNotECPublicKey = errors.New("Key is not a valid ECDSA public key") +) + +func newSigningMethod(name string) *signingMethod { + switch name { + case "HS256": + return &signingMethod{Name: name, Hash: crypto.SHA256} + case "HS384": + return &signingMethod{Name: name, Hash: crypto.SHA384} + case "HS512": + return &signingMethod{Name: name, Hash: crypto.SHA512} + case "RS256": + return &signingMethod{Name: name, Hash: crypto.SHA256} + case "RS384": + return &signingMethod{Name: name, Hash: crypto.SHA384} + case "RS512": + return &signingMethod{Name: name, Hash: crypto.SHA512} + case "ES256": + return &signingMethod{Name: name, Hash: crypto.SHA256, KeySize: 32, CurveBits: 256} + case "ES384": + return &signingMethod{Name: name, Hash: crypto.SHA384, KeySize: 48, CurveBits: 384} + case "ES512": + return &signingMethod{Name: name, Hash: crypto.SHA512, KeySize: 66, CurveBits: 512} + default: + return nil + } +} + +func (m *signingMethod) Verify(signingString, signature string, key interface{}) error { + switch m.Name { + case "HS256", "HS384", "HS512": + { + // Verify the key is the right type + keyBytes, ok := key.([]byte) + if !ok { + return errInvalidKeyType + } + + // Decode signature, for comparison + sig, err := decodeSegment(signature) + if err != nil { + return err + } + + // Can we use the specified hashing method? + if !m.Hash.Available() { + return errHashUnavailable + } + + // This signing method is symmetric, so we validate the signature + // by reproducing the signature from the signing string and key, then + // comparing that against the provided signature. + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write([]byte(signingString)) + if !hmac.Equal(sig, hasher.Sum(nil)) { + return errSignatureInvalid + } + + // No validation errors. Signature is good. + return nil + } + case "RS256", "RS384", "RS512": + { + var err error + + // Decode the signature + var sig []byte + if sig, err = decodeSegment(signature); err != nil { + return err + } + + var rsaKey *rsa.PublicKey + var ok bool + + if rsaKey, ok = key.(*rsa.PublicKey); !ok { + return errInvalidKeyType + } + + // Create hasher + if !m.Hash.Available() { + return errHashUnavailable + } + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Verify the signature + return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig) + } + case "ES256", "ES384", "ES512": + { + var err error + + // Decode the signature + var sig []byte + if sig, err = decodeSegment(signature); err != nil { + return err + } + + // GetEmployee the key + var ecdsaKey *ecdsa.PublicKey + switch k := key.(type) { + case *ecdsa.PublicKey: + ecdsaKey = k + default: + return errInvalidKeyType + } + + if len(sig) != 2*m.KeySize { + return errECDSAVerification + } + + r := big.NewInt(0).SetBytes(sig[:m.KeySize]) + s := big.NewInt(0).SetBytes(sig[m.KeySize:]) + + // Create hasher + if !m.Hash.Available() { + return errHashUnavailable + } + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Verify the signature + if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { + return nil + } + + return errECDSAVerification + } + default: + return errInvalidSigningMethod + } +} + +func (m *signingMethod) Sign(signingString string, key interface{}) (string, error) { + switch m.Name { + case "HS256", "HS384", "HS512": + { + if keyBytes, ok := key.([]byte); ok { + if !m.Hash.Available() { + return "", errHashUnavailable + } + + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write([]byte(signingString)) + + return encodeSegment(hasher.Sum(nil)), nil + } + + return "", errInvalidKeyType + } + case "RS256", "RS384", "RS512": + { + var rsaKey *rsa.PrivateKey + var ok bool + + // Validate type of key + if rsaKey, ok = key.(*rsa.PrivateKey); !ok { + return "", errInvalidKey + } + + // Create the hasher + if !m.Hash.Available() { + return "", errHashUnavailable + } + + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Sign the string and return the encoded bytes + if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil { + return encodeSegment(sigBytes), nil + } else { + return "", err + } + } + case "ES256", "ES384", "ES512": + { + // GetEmployee the key + var ecdsaKey *ecdsa.PrivateKey + switch k := key.(type) { + case *ecdsa.PrivateKey: + ecdsaKey = k + default: + return "", errInvalidKeyType + } + + // Create the hasher + if !m.Hash.Available() { + return "", errHashUnavailable + } + + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Sign the string and return r, s + if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { + curveBits := ecdsaKey.Curve.Params().BitSize + + if m.CurveBits != curveBits { + return "", errInvalidKey + } + + keyBytes := curveBits / 8 + if curveBits%8 > 0 { + keyBytes++ + } + + // We serialize the outpus (r and s) into big-endian byte arrays and pad + // them with zeros on the left to make sure the sizes work out. Both arrays + // must be keyBytes long, and the output must be 2*keyBytes long. + rBytes := r.Bytes() + rBytesPadded := make([]byte, keyBytes) + copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) + + sBytes := s.Bytes() + sBytesPadded := make([]byte, keyBytes) + copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + + out := append(rBytesPadded, sBytesPadded...) + + return encodeSegment(out), nil + } else { + return "", err + } + } + default: + { + return "", errInvalidSigningMethod + } + } +} + +func methodEnable(method string) bool { + if method == "HS256" || method == "HS384" || method == "HS512" || method == "RS256" || method == "RS384" || method == "RS512" || method == "ES256" || method == "ES384" || method == "ES512" { + return true + } + return false +} + +// Decode JWT specific base64url encoding with padding stripped +func decodeSegment(seg string) ([]byte, error) { + if l := len(seg) % 4; l > 0 { + seg += strings.Repeat("=", 4-l) + } + + return base64.URLEncoding.DecodeString(seg) +} + +// encode JWT specific base64url encoding with padding stripped +func encodeSegment(seg []byte) string { + return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") +} + +//ParseRSAPublicKeyFromPEM parse PEM encoded PKCS1 or PKCS8 public key +func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { + var err error + + // parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, errKeyMustBePEMEncoded + } + + // parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + var pkey *rsa.PublicKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { + return nil, errNotRSAPublicKey + } + + return pkey, nil +} + +//ParseECPublicKeyFromPEM parse PEM encoded PKCS1 or PKCS8 public key +func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { + var err error + + // parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, errKeyMustBePEMEncoded + } + + // parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + var pkey *ecdsa.PublicKey + var ok bool + if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok { + return nil, errNotECPublicKey + } + + return pkey, nil +} + +// base64解密 +func b64Decode(input string) (string, error) { + remainder := len(input) % 4 + // base64编码需要为4的倍数,如果不是4的倍数,则填充"="号 + if remainder > 0 { + padlen := 4 - remainder + input = input + strings.Repeat("=", padlen) + } + // 将原字符串中的"_","-"分别用"/"和"+"替换 + input = strings.Replace(strings.Replace(input, "_", "/", -1), "-", "+", -1) + result, err := base64.StdEncoding.DecodeString(input) + return string(result), err +} + +// 根据"."分割token字符串 +func tokenize(token string) []string { + parts := strings.Split(token, ".") + if len(parts) == 3 { + return parts + } + + return nil +} + +// 解析token,将token信息解析为jwtToken对象 +func decodeToken(token string) (*jwtToken, error) { + tokenParts := tokenize(token) + if tokenParts == nil { + return nil, errors.New("[jwt_auth] Invalid token") + } + header64 := tokenParts[0] + claims64 := tokenParts[1] + signature64 := tokenParts[2] + var header, claims map[string]interface{} + var signature string + headerD64, err := b64Decode(header64) + if err != nil { + return nil, errors.New("[jwt_auth] Invalid base64 encoded JSON") + } + + if err = json.Unmarshal([]byte(headerD64), &header); err != nil { + return nil, errors.New("[jwt_auth] Invalid JSON") + } + claimsD64, err := b64Decode(claims64) + if err != nil { + return nil, errors.New("[jwt_auth] Invalid base64 encoded JSON") + } + if err = json.Unmarshal([]byte(claimsD64), &claims); err != nil { + return nil, errors.New("[jwt_auth] Invalid JSON") + } + signature, err = b64Decode(signature64) + if err != nil { + return nil, errors.New("[jwt_auth] Invalid base64 encoded JSON") + } + if _, ok := header["typ"]; !ok || strings.ToUpper(header["typ"].(string)) != "JWT" { + return nil, errors.New("[jwt_auth] Invalid typ") + } + if _, ok := header["alg"]; !ok || !methodEnable(header["alg"].(string)) { + return nil, errors.New("[jwt_auth] Invalid alg") + } + if len(claims) == 0 { + return nil, errors.New("[jwt_auth] Invalid claims") + } + if len(signature) == 0 { + return nil, errors.New("[jwt_auth] Invalid signature") + } + return &jwtToken{Token: token, Header64: header64, Claims64: claims64, Signature64: signature64, Header: header, Claims: claims, Signature: signature}, nil +} + +//verifySignature 验证签名 +func verifySignature(token *jwtToken, key string) error { + + var k interface{} + switch token.Header["alg"].(string) { + case "HS256", "HS384", "HS512": + { + k = []byte(key) + } + case "RS256", "RS384", "RS512": + { + var err error + k, err = ParseRSAPublicKeyFromPEM([]byte(key)) + if err != nil { + return err + } + } + case "ES256", "ES384", "ES512": + { + var err error + k, err = ParseECPublicKeyFromPEM([]byte(key)) + if err != nil { + return err + } + } + default: + { + return errInvalidSigningMethod + } + } + return newSigningMethod(token.Header["alg"].(string)).Verify(token.Header64+"."+token.Claims64, token.Signature64, k) +} + +//verifyRegisteredClaims 验证签发字段 +func verifyRegisteredClaims(token *jwtToken, claimsToVerify []string) error { + if claimsToVerify == nil { + claimsToVerify = []string{} + } + + for _, claimName := range claimsToVerify { + var claim int64 = 0 + if _, ok := token.Claims[claimName]; ok { + + if typeOfData(token.Claims[claimName]) == reflect.Float64 { + claimFloat64, success := token.Claims[claimName].(float64) + if success { + claim = int64(claimFloat64) + } + } + + } + if claim < 1 { + return errors.New("[jwt_auth] " + claimName + " must be a number") + } + switch claimName { + case "nbf": + if claim > time.Now().Unix() { + return errors.New("[jwt_auth] token not valid yet") + } + case "exp": + if claim <= time.Now().Unix() { + return errors.New("[jwt_auth] token expired") + } + default: + return errors.New("[jwt_auth] Invalid claims") + } + } + return nil +} + +//获取数据的类型 +func typeOfData(data interface{}) reflect.Kind { + value := reflect.ValueOf(data) + valueType := value.Kind() + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + return valueType +} + +//retrieveJWTToken 获取jwtToken字符串 +func (j *jwt) retrieveJWTToken(context http_service.IHttpContext) (string, error) { + const tokenName = "jwt_token" + if authorizationHeader := context.Request().Header().GetHeader("Authorization"); authorizationHeader != "" { + if j.hideCredentials { + context.Proxy().Header().DelHeader("Authorization") + } + if strings.Contains(authorizationHeader, "bearer ") { + authorizationHeader = authorizationHeader[7:] + } + return authorizationHeader, nil + } + + if value := context.Proxy().URI().GetQuery(tokenName); value != "" { + if j.hideCredentials { + context.Proxy().URI().DelQuery(tokenName) + } + return value, nil + } + + formData, err := context.Proxy().Body().BodyForm() + if err != nil { + return "", errors.New("[jwt_auth] cannot find token in request") + } + if value, ok := formData[tokenName]; ok { + if j.hideCredentials { + delete(formData, tokenName) + context.Proxy().Body().SetForm(formData) + } + return value[0], nil + } + return "", errors.New("[jwt_auth] cannot find token in request") +} + +//doJWTAuthentication 进行JWT鉴权 +func (j *jwt) doJWTAuthentication(context http_service.IHttpContext) error { + tokenStr, err := j.retrieveJWTToken(context) + if err != nil { + return errors.New("[jwt_auth] Unrecognizable token") + } + token, err := decodeToken(tokenStr) + if err != nil { + return errors.New("[jwt_auth] Bad token; " + err.Error()) + } + + key := "" + keyClaimName := "iss" + if _, ok := token.Claims[keyClaimName]; ok { + key = token.Claims[keyClaimName].(string) + } else if _, ok = token.Header[keyClaimName]; ok { + key = token.Header[keyClaimName].(string) + } + + if key == "" { + return errors.New("[jwt_auth] No mandatory " + keyClaimName + " in claims") + } + + // 从配置中获取jwt凭证配置 + + jwtSecret, err := loadCredential(context, j.credentials, key, token.Header["alg"].(string)) + if err != nil { + return errors.New("[jwt_auth] No credentials found for given " + keyClaimName) + } + + jwtSecretValue := jwtSecret.RSAPublicKey + algorithm := "HS256" + if jwtSecret.Algorithm != "" { + algorithm = jwtSecret.Algorithm + } + if algorithm == "HS256" || algorithm == "HS384" || algorithm == "HS512" { + jwtSecretValue = jwtSecret.Secret + } + if j.signatureIsBase64 { + jwtSecretValue, err = b64Decode(jwtSecretValue) + if err != nil { + return errors.New("[jwt_auth] Invalid key/secret") + } + } + if jwtSecretValue == "" { + return errors.New("[jwt_auth] Invalid key/secret") + } + + if err = verifySignature(token, jwtSecretValue); err != nil { + return errors.New("[jwt_auth] Invalid signature") + } + if err = verifyRegisteredClaims(token, j.claimsToVerify); err != nil { + return err + } + return nil +} + +// 从配置中获取jwt凭证配置 +func loadCredential(context http_service.IHttpContext, conf *jwtUsers, key, alg string) (JwtCredential, error) { + + for _, credential := range conf.credentials { + if credential.Iss == key { + if credential.Algorithm == alg { + //将label set进context + for k, v := range credential.Labels { + context.SetLabel(k, v) + } + + return credential, nil + } + } + } + return JwtCredential{}, errors.New("[jwt_auth] Invalid jwt secret key") +} diff --git a/auth/auth_test.go b/auth/auth_test.go deleted file mode 100644 index f79810e8..00000000 --- a/auth/auth_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package auth - -import ( - "fmt" - "github.com/eolinker/eosc" - "testing" -) - -func TestAuth(t *testing.T) { - log.Debug(config.TypeNameOf((*IAuth)(nil))) -} diff --git a/drivers/plugins/app/app.go b/drivers/plugins/app/app.go new file mode 100644 index 00000000..78fc9470 --- /dev/null +++ b/drivers/plugins/app/app.go @@ -0,0 +1,35 @@ +package app + +import ( + "github.com/eolinker/apinto/auth" + "github.com/eolinker/eosc" +) + +type App struct { + auths []auth.IAuth +} + +func (a *App) Id() string { + //TODO implement me + panic("implement me") +} + +func (a *App) Start() error { + //TODO implement me + panic("implement me") +} + +func (a *App) Reset(conf interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + //TODO implement me + panic("implement me") +} + +func (a *App) Stop() error { + //TODO implement me + panic("implement me") +} + +func (a *App) CheckSkill(skill string) bool { + //TODO implement me + panic("implement me") +} diff --git a/drivers/plugins/app/config.go b/drivers/plugins/app/config.go new file mode 100644 index 00000000..64ba3d8a --- /dev/null +++ b/drivers/plugins/app/config.go @@ -0,0 +1,52 @@ +package app + +import ( + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/common/bean" + "sync" +) + +var ( + extenders eosc.IExtenderDrivers + ones sync.Once +) + +type Config struct { + Auth []*AuthConfig `json:"auth" label:"鉴权列表"` +} + +type AuthConfig struct { + Config interface{} `json:"config"` + DriverID string `json:"driver_id"` + Position string `json:"position"` + TokenName string `json:"token_name"` + Users []*User `json:"users"` +} + +type User struct { + Expire int `json:"expire"` + Labels map[string]string `json:"labels"` + Pattern string `json:"pattern"` + HideCredential bool `json:"hide_credential"` +} + +func Check(v interface{}) (*Config, error) { + ones.Do(func() { + bean.Autowired(&extenders) + }) + cfg, ok := v.(*Config) + if !ok { + return nil, eosc.ErrorConfigFieldUnknown + } + for _, a := range cfg.Auth { + fac, has := extenders.GetDriver(a.DriverID) + if !has { + return nil, eosc.ErrorDriverNotExist + } + driver, err := fac.Create("auth", "", "", "", nil) + if err != nil { + return nil, err + } + } + return nil, nil +} diff --git a/drivers/plugins/app/driver.go b/drivers/plugins/app/driver.go new file mode 100644 index 00000000..784a1909 --- /dev/null +++ b/drivers/plugins/app/driver.go @@ -0,0 +1,63 @@ +package app + +import ( + "fmt" + "github.com/eolinker/eosc/log" + "reflect" + + "github.com/eolinker/apinto/auth" + "github.com/eolinker/eosc" +) + +type Driver struct { + profession string + name string + label string + desc string + workers eosc.IWorkers + configType reflect.Type +} + +func (d *Driver) Check(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + _, err := d.check(v) + if err != nil { + return err + } + return nil +} + +func (d *Driver) check(v interface{}) (*Config, error) { + conf, ok := v.(*Config) + if !ok { + return nil, eosc.ErrorConfigFieldUnknown + } + return conf, nil +} + +func (d *Driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *Driver) getList(auths []eosc.RequireId) ([]auth.IAuth, error) { + ls := make([]auth.IAuth, 0, len(auths)) + for _, id := range auths { + worker, has := d.workers.Get(string(id)) + if !has { + return nil, fmt.Errorf("%s:%w", id, eosc.ErrorWorkerNotExits) + } + if !worker.CheckSkill(auth.AuthSkill) { + return nil, fmt.Errorf("%s:%w:%s", id, eosc.ErrorTargetNotImplementSkill, auth.AuthSkill) + } + ls = append(ls, worker.(auth.IAuth)) + + } + return ls, nil +} +func (d *Driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) { + conf, err := d.check(v) + if err != nil { + return nil, err + } + log.Info(conf) + return nil, nil +} diff --git a/drivers/plugins/app/factory.go b/drivers/plugins/app/factory.go new file mode 100644 index 00000000..5814b33d --- /dev/null +++ b/drivers/plugins/app/factory.go @@ -0,0 +1,42 @@ +package app + +import ( + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/common/bean" + "github.com/eolinker/eosc/utils/schema" + "reflect" +) + +const ( + Name = "app" +) + +func Register(register eosc.IExtenderDriverRegister) { + register.RegisterExtenderDriver(Name, NewFactory()) +} + +type Factory struct { +} + +func (f *Factory) Render() interface{} { + render, err := schema.Generate(reflect.TypeOf((*Config)(nil)), nil) + if err != nil { + return nil + } + return render +} +func NewFactory() *Factory { + return &Factory{} +} + +func (f *Factory) Create(profession string, name string, label string, desc string, params map[string]interface{}) (eosc.IExtenderDriver, error) { + d := &Driver{ + profession: profession, + name: name, + label: label, + desc: desc, + configType: reflect.TypeOf((*Config)(nil)), + } + bean.Autowired(&d.workers) + return d, nil +} diff --git a/drivers/plugins/app/manager.go b/drivers/plugins/app/manager.go new file mode 100644 index 00000000..b88aae31 --- /dev/null +++ b/drivers/plugins/app/manager.go @@ -0,0 +1,7 @@ +package app + +import "github.com/eolinker/eosc" + +type Manager struct { + AuthFactory map[string]eosc.IExtenderDriverFactory +} diff --git a/drivers/plugins/params-transformer/factory.go b/drivers/plugins/params-transformer/factory.go index 91ee686d..ade65d8e 100644 --- a/drivers/plugins/params-transformer/factory.go +++ b/drivers/plugins/params-transformer/factory.go @@ -1,6 +1,7 @@ package params_transformer import ( + "github.com/eolinker/eosc/log" "github.com/eolinker/eosc/utils/schema" "reflect" @@ -12,6 +13,7 @@ const ( ) func Register(register eosc.IExtenderDriverRegister) { + log.Debug("register params_transformer is ", Name) register.RegisterExtenderDriver(Name, NewFactory()) } diff --git a/plugin-manager/config.go b/plugin-manager/config.go index 2571f432..2d3ec09c 100644 --- a/plugin-manager/config.go +++ b/plugin-manager/config.go @@ -2,6 +2,7 @@ package plugin_manager import ( "fmt" + "github.com/eolinker/eosc/log" "github.com/eolinker/eosc/variable" "reflect" ) @@ -48,7 +49,7 @@ func (p *PluginConfig) GetType(originVal reflect.Value) (reflect.Type, error) { params = tmp } } - + log.Debug("plugin reset id is: ", id) factory, has := singleton.extenderDrivers.GetDriver(id) if !has { return nil, fmt.Errorf("driver(%s) not found", id)