go-ayo/common을 gocommon으로 분리
This commit is contained in:
199
azure/func.go
Normal file
199
azure/func.go
Normal file
@ -0,0 +1,199 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
type accessToken struct {
|
||||
sync.Mutex
|
||||
typetoken string
|
||||
expireAt time.Time
|
||||
url string
|
||||
values url.Values
|
||||
}
|
||||
|
||||
var jwkCache struct {
|
||||
headerlock sync.Mutex
|
||||
typetoken string
|
||||
expireAt time.Time
|
||||
pks map[string]*rsa.PublicKey
|
||||
}
|
||||
|
||||
func microsoftAppId() string {
|
||||
val := os.Getenv("MICROSOFT_APP_ID")
|
||||
if len(val) == 0 {
|
||||
val = "b5367590-5a94-4df3-bca0-ecd4b693ddf0"
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func microsoftAppPassword() string {
|
||||
val := os.Getenv("MICROSOFT_APP_PASSWORD")
|
||||
if len(val) == 0 {
|
||||
val = "~VG1cf2-~5Fw3Wz9_4.A.XxpZPO8BwJ36y"
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func getOpenIDConfiguration(x5t string) (*rsa.PublicKey, error) {
|
||||
// https://docs.microsoft.com/ko-kr/azure/bot-service/rest-api/bot-framework-rest-connector-authentication?view=azure-bot-service-4.0
|
||||
jwkCache.headerlock.Lock()
|
||||
defer jwkCache.headerlock.Unlock()
|
||||
|
||||
if time.Now().After(jwkCache.expireAt) {
|
||||
resp, err := http.Get("https://login.botframework.com/v1/.well-known/openidconfiguration")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
bt, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var doc map[string]interface{}
|
||||
if err = json.Unmarshal(bt, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := doc["jwks_uri"].(string)
|
||||
resp, err = http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
bt, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bt, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := doc["keys"].([]interface{})
|
||||
newPks := make(map[string]*rsa.PublicKey)
|
||||
for _, key := range keys {
|
||||
keydoc := key.(map[string]interface{})
|
||||
x5t := keydoc["x5t"].(string)
|
||||
eb := make([]byte, 4)
|
||||
nb, _ := base64.RawURLEncoding.DecodeString(keydoc["n"].(string))
|
||||
base64.RawURLEncoding.Decode(eb, []byte(keydoc["e"].(string)))
|
||||
n := big.NewInt(0).SetBytes(nb)
|
||||
e := binary.LittleEndian.Uint32(eb)
|
||||
pk := &rsa.PublicKey{
|
||||
N: n,
|
||||
E: int(e),
|
||||
}
|
||||
newPks[x5t] = pk
|
||||
}
|
||||
|
||||
jwkCache.expireAt = time.Now().Add(24 * time.Hour)
|
||||
jwkCache.pks = newPks
|
||||
}
|
||||
|
||||
return jwkCache.pks[x5t], nil
|
||||
}
|
||||
|
||||
func VerifyJWT(header string) error {
|
||||
if !strings.HasPrefix(header, "Bearer ") {
|
||||
return errors.New("invalid token")
|
||||
}
|
||||
tokenString := header[7:]
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return getOpenIDConfiguration(token.Header["x5t"].(string))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
if claims["iss"].(string) != "https://api.botframework.com" {
|
||||
return errors.New("issuer is not valid")
|
||||
}
|
||||
if claims["aud"].(string) != microsoftAppId() {
|
||||
return errors.New("audience is not valid")
|
||||
}
|
||||
expireAt := int64(claims["exp"].(float64))
|
||||
if math.Abs(float64((expireAt-time.Now().UTC().Unix())/int64(time.Second))) >= 300 {
|
||||
return errors.New("token expired")
|
||||
}
|
||||
} else {
|
||||
return errors.New("VerifyJWT token claims failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (at *accessToken) getAuthoizationToken() (string, error) {
|
||||
at.Lock()
|
||||
defer at.Unlock()
|
||||
|
||||
if len(at.url) == 0 {
|
||||
at.url = "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token"
|
||||
at.values = url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {microsoftAppId()},
|
||||
"scope": {"https://api.botframework.com/.default"},
|
||||
"client_secret": {microsoftAppPassword()},
|
||||
}
|
||||
}
|
||||
|
||||
if time.Now().After(at.expireAt) {
|
||||
resp, err := http.PostForm(at.url, at.values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var doc map[string]interface{}
|
||||
err = json.Unmarshal(body, &doc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if v, ok := doc["error"]; ok {
|
||||
if desc, ok := doc["error_description"]; ok {
|
||||
return "", errors.New(desc.(string))
|
||||
}
|
||||
|
||||
return "", errors.New(v.(string))
|
||||
}
|
||||
|
||||
tokenType := doc["token_type"].(string)
|
||||
token := doc["access_token"].(string)
|
||||
expin := doc["expires_in"]
|
||||
|
||||
var tokenDur int
|
||||
switch expin := expin.(type) {
|
||||
case float64:
|
||||
tokenDur = int(expin)
|
||||
case string:
|
||||
tokenDur, _ = strconv.Atoi(expin)
|
||||
}
|
||||
|
||||
at.typetoken = tokenType + " " + token
|
||||
at.expireAt = time.Now().Add(time.Duration(tokenDur) * time.Second)
|
||||
}
|
||||
|
||||
return at.typetoken, nil
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user