Explorar el Código

开放平台认证

baozhensong hace 6 años
padre
commit
f768e802b4

+ 2 - 0
pkg/models/application.go

@@ -12,6 +12,8 @@ type Application struct {
 	gorm.Model
 	// App-Key for api
 	AppKey string `sql:"type:varchar(200);not null;"`
+	//Secret-Key
+	SecretKey string `sql:"type:varchar(200);not null;"`
 	// App-Token for web hook
 	AppToken string `sql:"type:varchar(200);not null;"`
 	// Report Url for web hook

+ 6 - 0
pkg/rpcs/registry.go

@@ -13,6 +13,12 @@ type ArgsDeviceAuth struct {
 	DeviceID int64
 }
 
+// ArgsDeviceAuth device auth
+type ArgsAppAuth struct {
+	AppKey    string
+	Secretkey string
+}
+
 // ArgsDeviceUpdate device update args
 type ArgsDeviceUpdate struct {
 	DeviceIdentifier  string

+ 54 - 3
pkg/token/token.go

@@ -2,15 +2,17 @@ package token
 
 import (
 	"errors"
+	"reflect"
 	"sparrow/pkg/generator"
 	"sparrow/pkg/redispool"
-	"reflect"
 	"strconv"
 )
 
 const (
-	DeviceTokenKeyPrefix = "device:token:"
-	DeviceTokenExpires   = 7200
+	DeviceTokenKeyPrefix      = "device:token:"
+	DeviceTokenExpires        = 7200
+	ApplicationTokenKeyPrefix = "app:token:"
+	ApplicationTokenExpires   = 7200
 )
 
 type Helper struct {
@@ -90,3 +92,52 @@ func (helper *Helper) ClearToken(id uint64) error {
 
 	return nil
 }
+
+func (helper *Helper) GenerateAppToken(id uint64, key string) ([]byte, error) {
+	token, err := generator.GenRandomToken()
+	if err != nil {
+		return nil, err
+	}
+
+	conn, err := redispool.GetClient(helper.redishost)
+	if err != nil {
+		return nil, err
+	}
+
+	//key := ApplicationTokenKeyPrefix + strconv.FormatUint(id, 10)
+
+	_, err = conn.Do("SET", key, token)
+	if err != nil {
+		return nil, err
+	}
+	_, err = conn.Do("EXPIRE", key, DeviceTokenExpires)
+	if err != nil {
+		return nil, err
+	}
+
+	return token, nil
+
+}
+
+func (helper *Helper) ValidateAppToken(key string, token []byte) error {
+	conn, err := redispool.GetClient(helper.redishost)
+	if err != nil {
+		return err
+	}
+
+	readToken, err := conn.Do("GET", key)
+	if err != nil {
+		return err
+	}
+
+	if !reflect.DeepEqual(readToken, token) {
+		return errors.New("token not match.")
+	}
+
+	_, err = conn.Do("EXPIRE", key, DeviceTokenExpires)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 38 - 0
services/apiprovider/actions.go

@@ -31,6 +31,7 @@ const (
 	ErrWrongQueryFormat   = 10007
 	ErrAccessDenied       = 10008
 	ErrIllegalityAction   = 10009 //非法操作
+	ErrWrongSecret        = 10010 //
 )
 
 var (
@@ -286,3 +287,40 @@ func AddRule(device *models.Device, req *http.Request, r render.Render) {
 	return
 
 }
+
+func AppAuth(req *http.Request, r render.Render) {
+
+	var ruleReq rpcs.ArgsAppAuth
+	decoder := json.NewDecoder(req.Body)
+	err := decoder.Decode(&ruleReq)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	span, ctx := opentracing.StartSpanFromContext(context.Background(), "DeveloperRegist")
+	defer span.Finish()
+	ext.SpanKindRPCClient.Set(span)
+
+	app := &models.Application{}
+
+	err = server.RPCCallByName(ctx, "registry", "Registry.FindApplicationByAppKey", ruleReq, app)
+	if err != nil {
+
+		r.JSON(http.StatusOK, renderError(ErrWrongSecret, errors.New("Invalid secret key")))
+		return
+	}
+
+	if app.SecretKey != ruleReq.Secretkey {
+		// device secret is wrong.
+		r.JSON(http.StatusOK, renderError(ErrWrongSecret, errors.New("wrong application secret.")))
+		return
+	}
+
+	result := AppAuthDataResponse{
+		AccessToken: TokenMaker(app),
+	}
+
+	r.JSON(http.StatusOK, result)
+	return
+}

+ 4 - 0
services/apiprovider/response.go

@@ -25,3 +25,7 @@ type DeviceStatusResponse struct {
 	Common
 	Data DeviceStatusData `json:"data"`
 }
+
+type AppAuthDataResponse struct {
+	AccessToken string `json:"access_token"`
+}

+ 53 - 24
services/apiprovider/router.go

@@ -1,9 +1,33 @@
 package main
 
 import (
+	"fmt"
+	"net/http"
+
+	jwt "github.com/dgrijalva/jwt-go"
+	"github.com/dgrijalva/jwt-go/request"
 	"github.com/go-martini/martini"
 )
 
+func ValidateTokenMiddleware(w http.ResponseWriter, r *http.Request, c martini.Context) {
+
+	token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor,
+		func(token *jwt.Token) (interface{}, error) {
+			return []byte(SignedString), nil
+		})
+	if err == nil {
+		if token.Valid {
+			c.Next()
+		} else {
+			w.WriteHeader(http.StatusUnauthorized)
+			fmt.Fprint(w, "Token is not valid")
+		}
+	} else {
+		w.WriteHeader(http.StatusUnauthorized)
+		fmt.Fprint(w, "Unauthorized access to this resource")
+	}
+}
+
 // martini router
 func route(m *martini.ClassicMartini) {
 
@@ -15,36 +39,41 @@ func route(m *martini.ClassicMartini) {
 	// 	SigningMethod: jwt.SigningMethodHS256,
 	// })
 
-	// find a device by key
-	m.Get("/application/v1/device/info", GetDeviceInfoByKey)
+	m.Group("/application/v1", func(r martini.Router) {
+		// find a device by key
+		r.Get("/device/info", GetDeviceInfoByKey)
+
+		// find a device by identifier
+		r.Get("/devices/:identifier/info", ApplicationAuthOnDeviceIdentifer, GetDeviceInfoByIdentifier)
+
+		// get devie current status
+		r.Get("/devices/:identifier/status/current",
+			ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+			GetDeviceCurrentStatus)
 
-	// find a device by identifier
-	m.Get("/application/v1/devices/:identifier/info", ApplicationAuthOnDeviceIdentifer, GetDeviceInfoByIdentifier)
+		// get devie latest status
+		r.Get("/devices/:identifier/status/latest",
+			ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+			GetDeviceLatestStatus)
 
-	// get devie current status
-	m.Get("/application/v1/devices/:identifier/status/current",
-		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
-		GetDeviceCurrentStatus)
+		// set device status
+		r.Put("/devices/:identifier/status",
+			ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+			SetDeviceStatus)
 
-	// get devie latest status
-	m.Get("/application/v1/devices/:identifier/status/latest",
-		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
-		GetDeviceLatestStatus)
+		// send a command to device
+		r.Post("/devices/:identifier/commands",
+			ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+			SendCommandToDevice)
 
-	// set device status
-	m.Put("/application/v1/devices/:identifier/status",
-		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
-		SetDeviceStatus)
+		// and a rule to device
+		r.Post("/devices/:identifier/rules",
+			ApplicationAuthOnDeviceIdentifer, CheckDeviceIdentifier,
+			AddRule)
 
-	// send a command to device
-	m.Post("/application/v1/devices/:identifier/commands",
-		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
-		SendCommandToDevice)
+	}, ValidateTokenMiddleware)
 
-	// and a rule to device
-	m.Post("/application/v1/devices/:identifier/rules",
-		ApplicationAuthOnDeviceIdentifer, CheckDeviceIdentifier,
-		AddRule)
+	m.Post("/application/auth", AppAuth)
 	// // user login
 	// m.Post("/api/v1/login", binding.Bind(models.LoginRequest{}),
 	// 	UserLogin)

+ 36 - 0
services/apiprovider/token.go

@@ -0,0 +1,36 @@
+package main
+
+import (
+	"sparrow/pkg/models"
+	"time"
+
+	jwt "github.com/dgrijalva/jwt-go"
+)
+
+const SignedString = "www.yehaoji.com"
+
+type AppClaims struct {
+	AppID     uint
+	AppName   string
+	AppKey    string
+	SecretKey string
+	VendorID  uint
+	jwt.StandardClaims
+}
+
+// TokenMaker 生成token
+func TokenMaker(app *models.Application) string {
+	claims := AppClaims{
+		AppID:     app.ID,
+		AppName:   app.AppName,
+		AppKey:    app.AppKey,
+		SecretKey: app.SecretKey,
+		VendorID:  app.VendorID,
+	}
+	claims.ExpiresAt = time.Now().Add(time.Hour * 24).Unix()
+	claims.IssuedAt = time.Now().Unix()
+	claims.Issuer = "apiprovider"
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	ser, _ := token.SignedString([]byte(SignedString))
+	return ser
+}

+ 2 - 0
services/knowoapi/services/application.go

@@ -44,6 +44,8 @@ func (a appService) Create(app *models.Application) error {
 		return err
 	}
 	app.AppKey, _ = a.keyGen.GenRandomKey(int64(app.ID))
+	app.SecretKey, _ = generator.GenRandomPassword()
+
 	return a.model.Application.Create(app)
 }
 

+ 21 - 0
services/registry/registry.go

@@ -260,6 +260,27 @@ func (r *Registry) FindApplication(id int32, reply *models.Application) error {
 	return nil
 }
 
+//AppAuth will find the application with given args
+func (r *Registry) FindApplicationByAppKey(args *rpcs.ArgsAppAuth, reply *models.Application) error {
+
+	db, err := getDB()
+	if err != nil {
+		return err
+	}
+
+	a := &models.Application{}
+	a.AppKey = args.AppKey
+	a.SecretKey = args.Secretkey
+
+	err = db.Where(a).First(&reply).Error
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // RegisterDevice try to register a device to our platform.
 // if the device has already been registered,
 // the registration will success return the registered device before.

+ 34 - 5
services/registry/registry_test.go

@@ -1,6 +1,8 @@
 package main
 
 import (
+	"encoding/json"
+	"fmt"
 	"sparrow/pkg/models"
 	"sparrow/pkg/mysql"
 	"sparrow/pkg/rpcs"
@@ -159,7 +161,10 @@ func testDevice(t *testing.T, r *Registry) {
 	t.Log(device)
 
 	founddev := &models.Device{}
-	err = r.FindDeviceById(int64(device.ID), founddev)
+	arg := &rpcs.ArgsDeviceAuth{
+		DeviceID: int64(int(device.ID)),
+	}
+	err = r.FindDeviceById(arg, founddev)
 	if err != nil {
 		t.Error(err)
 	}
@@ -206,13 +211,37 @@ func TestRegistry(t *testing.T) {
 	*confAESKey = "ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP"
 
 	*confDBPass = "123456"
-	// r, err := NewRegistry()
-	// if err != nil {
-	// 	t.Fatal(err)
-	// }
+	r, err := NewRegistry()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testAppAuth(t, r)
 
 	//testVendor(t, r)
 	//testProduct(t, r)
 	//testApplication(t, r)
 	//testDevice(t, r)
 }
+
+func testAppAuth(t *testing.T, r *Registry) {
+
+	arg := &rpcs.ArgsAppAuth{
+		AppKey:    "aab1f679672380cf8cc79816bac4256809d0820473bc958e4c4eac91fd97e27bss",
+		Secretkey: "S0gyxgdve3n7LY3FWuysSLqLft65NauC",
+	}
+	a := &models.Application{}
+	err := r.FindApplicationByAppKey(arg, a)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if a.ID > 0 {
+		buf, _ := json.Marshal(a)
+
+		fmt.Println("==============", string(buf))
+	}
+
+	fmt.Println("===============>>>>>>>>>>>>>")
+
+}

+ 7 - 0
vendor/github.com/dgrijalva/jwt-go/request/doc.go

@@ -0,0 +1,7 @@
+// Utility package for extracting JWT tokens from
+// HTTP requests.
+//
+// The main function is ParseFromRequest and it's WithClaims variant.
+// See examples for how to use the various Extractor implementations
+// or roll your own.
+package request

+ 81 - 0
vendor/github.com/dgrijalva/jwt-go/request/extractor.go

@@ -0,0 +1,81 @@
+package request
+
+import (
+	"errors"
+	"net/http"
+)
+
+// Errors
+var (
+	ErrNoTokenInRequest = errors.New("no token present in request")
+)
+
+// Interface for extracting a token from an HTTP request.
+// The ExtractToken method should return a token string or an error.
+// If no token is present, you must return ErrNoTokenInRequest.
+type Extractor interface {
+	ExtractToken(*http.Request) (string, error)
+}
+
+// Extractor for finding a token in a header.  Looks at each specified
+// header in order until there's a match
+type HeaderExtractor []string
+
+func (e HeaderExtractor) ExtractToken(req *http.Request) (string, error) {
+	// loop over header names and return the first one that contains data
+	for _, header := range e {
+		if ah := req.Header.Get(header); ah != "" {
+			return ah, nil
+		}
+	}
+	return "", ErrNoTokenInRequest
+}
+
+// Extract token from request arguments.  This includes a POSTed form or
+// GET URL arguments.  Argument names are tried in order until there's a match.
+// This extractor calls `ParseMultipartForm` on the request
+type ArgumentExtractor []string
+
+func (e ArgumentExtractor) ExtractToken(req *http.Request) (string, error) {
+	// Make sure form is parsed
+	req.ParseMultipartForm(10e6)
+
+	// loop over arg names and return the first one that contains data
+	for _, arg := range e {
+		if ah := req.Form.Get(arg); ah != "" {
+			return ah, nil
+		}
+	}
+
+	return "", ErrNoTokenInRequest
+}
+
+// Tries Extractors in order until one returns a token string or an error occurs
+type MultiExtractor []Extractor
+
+func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) {
+	// loop over header names and return the first one that contains data
+	for _, extractor := range e {
+		if tok, err := extractor.ExtractToken(req); tok != "" {
+			return tok, nil
+		} else if err != ErrNoTokenInRequest {
+			return "", err
+		}
+	}
+	return "", ErrNoTokenInRequest
+}
+
+// Wrap an Extractor in this to post-process the value before it's handed off.
+// See AuthorizationHeaderExtractor for an example
+type PostExtractionFilter struct {
+	Extractor
+	Filter func(string) (string, error)
+}
+
+func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
+	if tok, err := e.Extractor.ExtractToken(req); tok != "" {
+		return e.Filter(tok)
+	} else {
+		return "", err
+	}
+}

+ 28 - 0
vendor/github.com/dgrijalva/jwt-go/request/oauth2.go

@@ -0,0 +1,28 @@
+package request
+
+import (
+	"strings"
+)
+
+// Strips 'Bearer ' prefix from bearer token string
+func stripBearerPrefixFromTokenString(tok string) (string, error) {
+	// Should be a bearer token
+	if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " {
+		return tok[7:], nil
+	}
+	return tok, nil
+}
+
+// Extract bearer token from Authorization header
+// Uses PostExtractionFilter to strip "Bearer " prefix from header
+var AuthorizationHeaderExtractor = &PostExtractionFilter{
+	HeaderExtractor{"Authorization"},
+	stripBearerPrefixFromTokenString,
+}
+
+// Extractor for OAuth2 access tokens.  Looks in 'Authorization'
+// header then 'access_token' argument for a token.
+var OAuth2Extractor = &MultiExtractor{
+	AuthorizationHeaderExtractor,
+	ArgumentExtractor{"access_token"},
+}

+ 68 - 0
vendor/github.com/dgrijalva/jwt-go/request/request.go

@@ -0,0 +1,68 @@
+package request
+
+import (
+	"github.com/dgrijalva/jwt-go"
+	"net/http"
+)
+
+// Extract and parse a JWT token from an HTTP request.
+// This behaves the same as Parse, but accepts a request and an extractor
+// instead of a token string.  The Extractor interface allows you to define
+// the logic for extracting a token.  Several useful implementations are provided.
+//
+// You can provide options to modify parsing behavior
+func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc, options ...ParseFromRequestOption) (token *jwt.Token, err error) {
+	// Create basic parser struct
+	p := &fromRequestParser{req, extractor, nil, nil}
+
+	// Handle options
+	for _, option := range options {
+		option(p)
+	}
+
+	// Set defaults
+	if p.claims == nil {
+		p.claims = jwt.MapClaims{}
+	}
+	if p.parser == nil {
+		p.parser = &jwt.Parser{}
+	}
+
+	// perform extract
+	tokenString, err := p.extractor.ExtractToken(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// perform parse
+	return p.parser.ParseWithClaims(tokenString, p.claims, keyFunc)
+}
+
+// ParseFromRequest but with custom Claims type
+// DEPRECATED: use ParseFromRequest and the WithClaims option
+func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc) (token *jwt.Token, err error) {
+	return ParseFromRequest(req, extractor, keyFunc, WithClaims(claims))
+}
+
+type fromRequestParser struct {
+	req       *http.Request
+	extractor Extractor
+	claims    jwt.Claims
+	parser    *jwt.Parser
+}
+
+type ParseFromRequestOption func(*fromRequestParser)
+
+// Parse with custom claims
+func WithClaims(claims jwt.Claims) ParseFromRequestOption {
+	return func(p *fromRequestParser) {
+		p.claims = claims
+	}
+}
+
+// Parse using a custom parser
+func WithParser(parser *jwt.Parser) ParseFromRequestOption {
+	return func(p *fromRequestParser) {
+		p.parser = parser
+	}
+}

+ 6 - 0
vendor/vendor.json

@@ -113,6 +113,12 @@
 			"revision": "0b96aaa707760d6ab28d9b9d1913ff5993328bae",
 			"revisionTime": "2018-07-19T21:18:23Z"
 		},
+		{
+			"checksumSHA1": "a/3TYNUV2NoU0EfUmJfd5+r/CLk=",
+			"path": "github.com/dgrijalva/jwt-go/request",
+			"revision": "3af4c746e1c248ee8491a3e0c6f7a9cd831e95f8",
+			"revisionTime": "2018-09-21T17:23:15Z"
+		},
 		{
 			"checksumSHA1": "uexCd9vEWeJgEXoeAjM7mLFZuIw=",
 			"origin": "github.com/kataras/iris/vendor/github.com/eknkc/amber",