mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 12:47:28 +08:00
feat:add oidc wasm plugin (#568)
This commit is contained in:
188
plugins/wasm-go/extensions/oidc/oc/cookie.go
Normal file
188
plugins/wasm-go/extensions/oidc/oc/cookie.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oc
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CookieData struct {
|
||||
IDToken string
|
||||
Secret string
|
||||
Nonce []byte
|
||||
CreatedAt time.Time
|
||||
ExpiresOn time.Time
|
||||
}
|
||||
|
||||
type CookieOption struct {
|
||||
Name string
|
||||
Domain string
|
||||
Secret string
|
||||
value string
|
||||
Path string
|
||||
SameSite string
|
||||
Expire time.Time
|
||||
Secure bool
|
||||
HTTPOnly bool
|
||||
}
|
||||
|
||||
// SerializeAndEncrypt 将 CookieData 对象序列化并加密为一个安全的cookie header
|
||||
func SerializeAndEncryptCookieData(data *CookieData, keySecret string, cookieSettings *CookieOption) (string, error) {
|
||||
return buildSecureCookieHeader(data, keySecret, cookieSettings)
|
||||
}
|
||||
|
||||
// DeserializedeCookieData 将一个安全的cookie header解密并反序列化为 CookieData 对象
|
||||
func DeserializedeCookieData(cookievalue string) (*CookieData, error) {
|
||||
|
||||
data, err := retrieveCookieData(cookievalue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if checkCookieExpiry(data) {
|
||||
return nil, fmt.Errorf("cookie is expired")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
func Set32Bytes(key string) string {
|
||||
const desiredLength = 32
|
||||
keyLength := len(key)
|
||||
|
||||
var adjustedKey string
|
||||
if keyLength > desiredLength {
|
||||
adjustedKey = key[:desiredLength]
|
||||
} else if keyLength < desiredLength {
|
||||
padding := strings.Repeat("0", desiredLength-keyLength)
|
||||
adjustedKey = key + padding
|
||||
} else {
|
||||
adjustedKey = key
|
||||
}
|
||||
return adjustedKey
|
||||
}
|
||||
|
||||
// 必须是16/24/32字节长
|
||||
func Decrypt(ciphertext string, key string) (string, error) {
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
decodedCiphertext, err := base64.URLEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(decodedCiphertext) < aes.BlockSize {
|
||||
return "", fmt.Errorf("ciphertext is too short")
|
||||
}
|
||||
|
||||
iv := decodedCiphertext[:aes.BlockSize]
|
||||
decodedCiphertext = decodedCiphertext[aes.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
|
||||
stream.XORKeyStream(decodedCiphertext, decodedCiphertext)
|
||||
|
||||
return string(decodedCiphertext), nil
|
||||
}
|
||||
|
||||
func encrypt(plainText string, key string) (string, error) {
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, aes.BlockSize+len(plainText))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(plainText))
|
||||
|
||||
return base64.URLEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
func buildSecureCookieHeader(data *CookieData, keySecret string, cookieSettings *CookieOption) (string, error) {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encryptedValue, err := encrypt(string(jsonData), keySecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encodedValue := url.QueryEscape(encryptedValue)
|
||||
cookieSettings.value = encodedValue
|
||||
|
||||
return generateCookie(cookieSettings), nil
|
||||
}
|
||||
|
||||
func retrieveCookieData(cookieValue string) (*CookieData, error) {
|
||||
var data CookieData
|
||||
err := json.Unmarshal([]byte(cookieValue), &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func generateCookie(settings *CookieOption) string {
|
||||
var secureFlag, httpOnlyFlag, sameSiteFlag string
|
||||
if settings.Secure {
|
||||
secureFlag = "Secure;"
|
||||
}
|
||||
|
||||
if settings.HTTPOnly {
|
||||
httpOnlyFlag = "HttpOnly;"
|
||||
}
|
||||
|
||||
if settings.SameSite != "" {
|
||||
sameSiteFlag = fmt.Sprintf("SameSite=%s;", settings.SameSite)
|
||||
}
|
||||
|
||||
expiresStr := settings.Expire.Format(time.RFC1123)
|
||||
maxAge := int(settings.Expire.Sub(time.Now()).Seconds())
|
||||
|
||||
cookie := fmt.Sprintf("%s=%s; Path=%s; Domain=%s; Expires=%s; Max-Age=%d; %s %s %s",
|
||||
settings.Name,
|
||||
settings.value,
|
||||
settings.Path,
|
||||
settings.Domain,
|
||||
expiresStr,
|
||||
maxAge,
|
||||
secureFlag,
|
||||
httpOnlyFlag,
|
||||
sameSiteFlag,
|
||||
)
|
||||
return cookie
|
||||
}
|
||||
|
||||
func checkCookieExpiry(data *CookieData) bool {
|
||||
return data.ExpiresOn.Before(time.Now())
|
||||
}
|
||||
Reference in New Issue
Block a user