Go implementa autenticação de solicitação AWS4 | Go Topic Month

O que é Amazon S3 ? É um serviço de armazenamento de objetos da AWS que oferece escalabilidade, disponibilidade de dados, segurança e desempenho líderes do setor. O Amazon S3 atinge 99,999999999% (11 9s) de durabilidade.

Usos do Amazon S3 AWS Signature Version 4(doravante denominado AWS4) para solicitações de autenticação. Este artigo explicará como usar Go para implementar a autenticação de solicitação AWS4.

AWS4 é um protocolo para autenticar solicitações de API de entrada para todos os serviços regionais da AWS.

AWS4

As solicitações de assinatura do AWS4 têm as seguintes vantagens (mas também depende de como você o usa):

  • Verifique a identidade do solicitante - Solicitações autenticadas AccessKeyIDexigem SecretAccessKeyassinaturas criadas com e .
  • Protegendo dados em trânsito - Para evitar a adulteração de solicitações em trânsito, alguns elementos de solicitação (por exemplo 请求路径, , 请求头etc.) podem ser usados ​​para calcular assinaturas de solicitação. Depois que o Amazon S3 recebe a solicitação, ele usa o mesmo elemento de solicitação para calcular a assinatura. Se algum componente de solicitação recebido pelo Amazon S3 não corresponder aos componentes usados ​​para calcular a assinatura, o Amazon S3 rejeitará a solicitação.
  • Impedir a reutilização da parte assinada da solicitação - A parte assinada da solicitação é válida por um período de tempo a partir do carimbo de data/hora na solicitação.

Método de autorização

  • Cabeçalhos de autenticação HTTP, como cabeçalhos de Authorizationsolicitação :
Authorization: AWS4-HMAC-SHA256 
Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, 
SignedHeaders=host;range;x-amz-date,
Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024
复制代码
  • Parâmetros de string de consulta de URL, como um URL pré-assinado:
https://s3.amazonaws.com/examplebucket/test.txt
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=<your-access-key-id>/20130721/us-east-1/s3/aws4_request
&X-Amz-Date=20130721T201207Z
&X-Amz-Expires=86400
&X-Amz-SignedHeaders=host
&X-Amz-Signature=<signature-value>
复制代码

Go implementa cabeçalhos de autenticação HTTP

Componentes do cabeçalho de autenticação HTTP

  • AWS4-HMAC-SHA256 - Esta string especifica AWS4 e o algoritmo de assinatura (HMAC-SHA256).
  • Credential - Especifique AccessKeyID, data, região e serviço para calcular a assinatura. Seu formato é <your-access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request, dateo formato é YYYYMMDD.
  • SignedHeaders - Especifica uma lista separada por ponto e vírgula de cabeçalhos de solicitação usados ​​para calcular a assinatura. Contém apenas o nome do cabeçalho da solicitação e deve estar em letras minúsculas, por exemplo: host;range;x-amz-date.
  • Assinatura - Uma assinatura de 256 bits representada como 64 caracteres hexadecimais minúsculos, por exemplo: fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024.

Carregar imagem para Nuggets

De acordo com o aprendizado da certificação de solicitação AWS4, e referindo-se ao código do simples3 , a função de upload imagens para Nuggets (byte-beating storage service, cujo nome de serviço é imagex) é realizada. O seguinte é parte da implementação do código (omitido principalmente ClientParte da implementação, o código completo será de código aberto após melhorias subsequentes):

package juejin

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/tidwall/gjson"
)

const (
	amzDateISO8601TimeFormat = "20060102T150405Z"
	shortTimeFormat          = "20060102"
	algorithm                = "AWS4-HMAC-SHA256"
	serviceName              = "imagex"
	serviceID                = "k3u1fbpfcp"
	version                  = "2018-08-01"
	uploadURLFormat          = "https://%s/%s"

	RegionCNNorth = "cn-north-1"

	actionApplyImageUpload  = "ApplyImageUpload"
	actionCommitImageUpload = "CommitImageUpload"

	polynomialCRC32 = 0xEDB88320
)

var (
	newLine = []byte{'\n'}

	// if object matches reserved string, no need to encode them
	reservedObjectNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
)

type ImageX struct {
	AccessKey string
	SecretKey string
	Region    string
	Client    *http.Client

	Token   string
	Version string
	BaseURL string
}

type UploadToken struct {
	AccessKeyID     string `json:"AccessKeyID"`
	SecretAccessKey string `json:"SecretAccessKey"`
	SessionToken    string `json:"SessionToken"`
}

func (c *Client) UploadImage(region, imgPath string) (string, error) {
	uploadToken, err := c.GetUploadToken()
	if err != nil {
		return "", err
	}

	ix := &ImageX{
		AccessKey: uploadToken.AccessKeyID,
		SecretKey: uploadToken.SecretAccessKey,
		Token:     uploadToken.SessionToken,
		Region:    region,
	}

	applyRes, err := ix.ApplyImageUpload()
	if err != nil {
		return "", err
	}

	storeInfo := gjson.Get(applyRes, "Result.UploadAddress.StoreInfos.0")
	storeURI := storeInfo.Get("StoreUri").String()
	storeAuth := storeInfo.Get("Auth").String()
	uploadHost := gjson.Get(applyRes, "Result.UploadAddress.UploadHosts.0").String()
	uploadURL := fmt.Sprintf(uploadURLFormat, uploadHost, storeURI)
	if err := ix.Upload(uploadURL, imgPath, storeAuth); err != nil {
		return "", err
	}

	sessionKey := gjson.Get(applyRes, "Result.UploadAddress.SessionKey").String()
	if _, err = ix.CommitImageUpload(sessionKey); err != nil {
		return "", err
	}

	return c.GetImageURL(storeURI)
}

func (c *Client) GetImageURL(uri string) (string, error) {
	endpoint := "/imagex/get_img_url"
	params := &url.Values{
		"uri": []string{uri},
	}
	raw, err := c.Get(APIBaseURL, endpoint, params)
	if err != nil {
		return "", err
	}
	rawurl := gjson.Get(raw, "data.main_url").String()
	return rawurl, nil
}

func (c *Client) GetUploadToken() (*UploadToken, error) {
	endpoint := "/imagex/gen_token"
	params := &url.Values{
		"client": []string{"web"},
	}
	raw, err := c.Get(APIBaseURL, endpoint, params)
	if err != nil {
		return nil, err
	}
	var token *UploadToken
	err = json.Unmarshal([]byte(gjson.Get(raw, "data.token").String()), &token)
	return token, err
}

func (ix *ImageX) ApplyImageUpload() (string, error) {
	rawurl := fmt.Sprintf("https://imagex.bytedanceapi.com/?Action=%s&Version=%s&ServiceId=%s",
		actionApplyImageUpload, version, serviceID)
	req, err := http.NewRequest(http.MethodGet, rawurl, nil)
	if err != nil {
		return "", err
	}

	if err := ix.signRequest(req); err != nil {
		return "", err
	}

	res, err := ix.getClient().Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	raw := string(b)
	if res.StatusCode != 200 || gjson.Get(raw, "ResponseMetadata.Error").Exists() {
		return "", fmt.Errorf("raw: %s, response: %+v", raw, res)
	}
	return raw, nil
}

func (ix *ImageX) CommitImageUpload(sessionKey string) (string, error) {
	rawurl := fmt.Sprintf("https://imagex.bytedanceapi.com/?Action=%s&Version=%s&SessionKey=%s&ServiceId=%s",
		actionCommitImageUpload, version, sessionKey, serviceID)
	req, err := http.NewRequest(http.MethodPost, rawurl, nil)
	if err != nil {
		return "", err
	}

	if err := ix.signRequest(req); err != nil {
		return "", err
	}

	res, err := ix.getClient().Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	raw := string(b)
	if res.StatusCode != 200 || gjson.Get(raw, "ResponseMetadata.Error").Exists() {
		return "", fmt.Errorf("raw: %s, response: %+v", raw, res)
	}
	return raw, nil
}

func (ix *ImageX) getClient() *http.Client {
	if ix.Client == nil {
		return http.DefaultClient
	}
	return ix.Client
}

func (ix *ImageX) signKeys(t time.Time) []byte {
	h := makeHMac([]byte("AWS4"+ix.SecretKey), []byte(t.Format(shortTimeFormat)))
	h = makeHMac(h, []byte(ix.Region))
	h = makeHMac(h, []byte(serviceName))
	h = makeHMac(h, []byte("aws4_request"))
	return h
}

func (ix *ImageX) writeRequest(w io.Writer, r *http.Request) error {
	r.Header.Set("host", r.Host)

	w.Write([]byte(r.Method))
	w.Write(newLine)
	writeURI(w, r)
	w.Write(newLine)
	writeQuery(w, r)
	w.Write(newLine)
	writeHeader(w, r)
	w.Write(newLine)
	w.Write(newLine)
	writeHeaderList(w, r)
	w.Write(newLine)
	return writeBody(w, r)
}

func (ix *ImageX) writeStringToSign(w io.Writer, t time.Time, r *http.Request) error {
	w.Write([]byte(algorithm))
	w.Write(newLine)
	w.Write([]byte(t.Format(amzDateISO8601TimeFormat)))
	w.Write(newLine)

	w.Write([]byte(ix.creds(t)))
	w.Write(newLine)

	h := sha256.New()
	if err := ix.writeRequest(h, r); err != nil {
		return err
	}
	fmt.Fprintf(w, "%x", h.Sum(nil))
	return nil
}

func (ix *ImageX) creds(t time.Time) string {
	return t.Format(shortTimeFormat) + "/" + ix.Region + "/" + serviceName + "/aws4_request"
}

func (ix *ImageX) signRequest(req *http.Request) error {
	t := time.Now().UTC()
	req.Header.Set("x-amz-date", t.Format(amzDateISO8601TimeFormat))

	req.Header.Set("x-amz-security-token", ix.Token)

	k := ix.signKeys(t)
	h := hmac.New(sha256.New, k)

	if err := ix.writeStringToSign(h, t, req); err != nil {
		return err
	}

	auth := bytes.NewBufferString(algorithm)
	auth.Write([]byte(" Credential=" + ix.AccessKey + "/" + ix.creds(t)))
	auth.Write([]byte{',', ' '})
	auth.Write([]byte("SignedHeaders="))
	writeHeaderList(auth, req)
	auth.Write([]byte{',', ' '})
	auth.Write([]byte("Signature=" + fmt.Sprintf("%x", h.Sum(nil))))

	req.Header.Set("authorization", auth.String())
	return nil
}

func writeURI(w io.Writer, r *http.Request) {
	path := r.URL.RequestURI()
	if r.URL.RawQuery != "" {
		path = path[:len(path)-len(r.URL.RawQuery)-1]
	}
	slash := strings.HasSuffix(path, "/")
	path = filepath.Clean(path)
	if path != "/" && slash {
		path += "/"
	}
	w.Write([]byte(path))
}
func writeQuery(w io.Writer, r *http.Request) {
	var a []string
	for k, vs := range r.URL.Query() {
		k = url.QueryEscape(k)
		for _, v := range vs {
			if v == "" {
				a = append(a, k)
			} else {
				v = url.QueryEscape(v)
				a = append(a, k+"="+v)
			}
		}
	}
	sort.Strings(a)
	for i, s := range a {
		if i > 0 {
			w.Write([]byte{'&'})
		}
		w.Write([]byte(s))
	}
}

func writeHeader(w io.Writer, r *http.Request) {
	i, a := 0, make([]string, len(r.Header))
	for k, v := range r.Header {
		sort.Strings(v)
		a[i] = strings.ToLower(k) + ":" + strings.Join(v, ",")
		i++
	}
	sort.Strings(a)
	for i, s := range a {
		if i > 0 {
			w.Write(newLine)
		}
		io.WriteString(w, s)
	}
}

func writeHeaderList(w io.Writer, r *http.Request) {
	i, a := 0, make([]string, len(r.Header))
	for k := range r.Header {
		a[i] = strings.ToLower(k)
		i++
	}
	sort.Strings(a)
	for i, s := range a {
		if i > 0 {
			w.Write([]byte{';'})
		}
		w.Write([]byte(s))
	}
}

func writeBody(w io.Writer, r *http.Request) error {
	var (
		b   []byte
		err error
	)
	// If the payload is empty, use the empty string as the input to the SHA256 function
	// http://docs.amazonwebservices.com/general/latest/gr/sigv4-create-canonical-request.html
	if r.Body == nil {
		b = []byte("")
	} else {
		b, err = ioutil.ReadAll(r.Body)
		if err != nil {
			return err
		}
		r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
	}

	h := sha256.New()
	h.Write(b)
	fmt.Fprintf(w, "%x", h.Sum(nil))
	return nil
}

func makeHMac(key []byte, data []byte) []byte {
	hash := hmac.New(sha256.New, key)
	hash.Write(data)
	return hash.Sum(nil)
}

func (ix *ImageX) Upload(rawurl, fp, auth string) error {
	crc32, err := hashFileCRC32(fp)
	if err != nil {
		return err
	}
	file, err := os.Open(fp)
	if err != nil {
		return err
	}
	defer file.Close()

	req, err := http.NewRequest(http.MethodPost, rawurl, file)
	if err != nil {
		return err
	}
	req.Header.Add("authorization", auth)
	req.Header.Add("Content-Type", "application/octet-stream")
	req.Header.Add("content-crc32", crc32)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return err
	}
	raw := string(b)
	if gjson.Get(raw, "success").Int() != 0 {
		return fmt.Errorf("raw: %s, response: %+v", raw, res)
	}
	return nil
}

// hashFileCRC32 generate CRC32 hash of a file
// Refer https://mrwaggel.be/post/generate-crc32-hash-of-a-file-in-golang-turorial/
func hashFileCRC32(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()
	tablePolynomial := crc32.MakeTable(polynomialCRC32)
	hash := crc32.New(tablePolynomial)
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}
	return hex.EncodeToString(hash.Sum(nil)), nil
}

复制代码

Resumir

Em uma frase, os Nuggets são touros!

Acho que você gosta

Origin juejin.im/post/6950300506946273294
Recomendado
Clasificación