github.com/astaxie/beego@v1.12.3/plugins/apiauth/apiauth.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package apiauth provides handlers to enable apiauth support.
    16  //
    17  // Simple Usage:
    18  //	import(
    19  //		"github.com/astaxie/beego"
    20  //		"github.com/astaxie/beego/plugins/apiauth"
    21  //	)
    22  //
    23  //	func main(){
    24  //		// apiauth every request
    25  //		beego.InsertFilter("*", beego.BeforeRouter,apiauth.APIBaiscAuth("appid","appkey"))
    26  //		beego.Run()
    27  //	}
    28  //
    29  // Advanced Usage:
    30  //
    31  //	func getAppSecret(appid string) string {
    32  //		// get appsecret by appid
    33  //		// maybe store in configure, maybe in database
    34  //	}
    35  //
    36  //	beego.InsertFilter("*", beego.BeforeRouter,apiauth.APISecretAuth(getAppSecret, 360))
    37  //
    38  // Information:
    39  //
    40  // In the request user should include these params in the query
    41  //
    42  // 1. appid
    43  //
    44  //		 appid is assigned to the application
    45  //
    46  // 2. signature
    47  //
    48  //	get the signature use apiauth.Signature()
    49  //
    50  //	when you send to server remember use url.QueryEscape()
    51  //
    52  // 3. timestamp:
    53  //
    54  //       send the request time, the format is yyyy-mm-dd HH:ii:ss
    55  //
    56  package apiauth
    57  
    58  import (
    59  	"bytes"
    60  	"crypto/hmac"
    61  	"crypto/sha256"
    62  	"encoding/base64"
    63  	"fmt"
    64  	"net/url"
    65  	"sort"
    66  	"time"
    67  
    68  	"github.com/astaxie/beego"
    69  	"github.com/astaxie/beego/context"
    70  )
    71  
    72  // AppIDToAppSecret is used to get appsecret throw appid
    73  type AppIDToAppSecret func(string) string
    74  
    75  // APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret
    76  func APIBasicAuth(appid, appkey string) beego.FilterFunc {
    77  	ft := func(aid string) string {
    78  		if aid == appid {
    79  			return appkey
    80  		}
    81  		return ""
    82  	}
    83  	return APISecretAuth(ft, 300)
    84  }
    85  
    86  // APIBaiscAuth calls APIBasicAuth for previous callers
    87  func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
    88  	return APIBasicAuth(appid, appkey)
    89  }
    90  
    91  // APISecretAuth use AppIdToAppSecret verify and
    92  func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
    93  	return func(ctx *context.Context) {
    94  		if ctx.Input.Query("appid") == "" {
    95  			ctx.ResponseWriter.WriteHeader(403)
    96  			ctx.WriteString("miss query param: appid")
    97  			return
    98  		}
    99  		appsecret := f(ctx.Input.Query("appid"))
   100  		if appsecret == "" {
   101  			ctx.ResponseWriter.WriteHeader(403)
   102  			ctx.WriteString("not exist this appid")
   103  			return
   104  		}
   105  		if ctx.Input.Query("signature") == "" {
   106  			ctx.ResponseWriter.WriteHeader(403)
   107  			ctx.WriteString("miss query param: signature")
   108  			return
   109  		}
   110  		if ctx.Input.Query("timestamp") == "" {
   111  			ctx.ResponseWriter.WriteHeader(403)
   112  			ctx.WriteString("miss query param: timestamp")
   113  			return
   114  		}
   115  		u, err := time.Parse("2006-01-02 15:04:05", ctx.Input.Query("timestamp"))
   116  		if err != nil {
   117  			ctx.ResponseWriter.WriteHeader(403)
   118  			ctx.WriteString("timestamp format is error, should 2006-01-02 15:04:05")
   119  			return
   120  		}
   121  		t := time.Now()
   122  		if t.Sub(u).Seconds() > float64(timeout) {
   123  			ctx.ResponseWriter.WriteHeader(403)
   124  			ctx.WriteString("timeout! the request time is long ago, please try again")
   125  			return
   126  		}
   127  		if ctx.Input.Query("signature") !=
   128  			Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URL()) {
   129  			ctx.ResponseWriter.WriteHeader(403)
   130  			ctx.WriteString("auth failed")
   131  		}
   132  	}
   133  }
   134  
   135  // Signature used to generate signature with the appsecret/method/params/RequestURI
   136  func Signature(appsecret, method string, params url.Values, RequestURL string) (result string) {
   137  	var b bytes.Buffer
   138  	keys := make([]string, len(params))
   139  	pa := make(map[string]string)
   140  	for k, v := range params {
   141  		pa[k] = v[0]
   142  		keys = append(keys, k)
   143  	}
   144  
   145  	sort.Strings(keys)
   146  
   147  	for _, key := range keys {
   148  		if key == "signature" {
   149  			continue
   150  		}
   151  
   152  		val := pa[key]
   153  		if key != "" && val != "" {
   154  			b.WriteString(key)
   155  			b.WriteString(val)
   156  		}
   157  	}
   158  
   159  	stringToSign := fmt.Sprintf("%v\n%v\n%v\n", method, b.String(), RequestURL)
   160  
   161  	sha256 := sha256.New
   162  	hash := hmac.New(sha256, []byte(appsecret))
   163  	hash.Write([]byte(stringToSign))
   164  	return base64.StdEncoding.EncodeToString(hash.Sum(nil))
   165  }