slimauth

package
v0.8.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2024 License: MIT Imports: 15 Imported by: 2

Documentation

Overview

slimauth 实现 SlimAuth 协议,它是带有签名校验逻辑的 SlimAPI 的扩展。

但是和 SlimAPI 相比,当前不支持 multipart/form-data 类型的请求。

签名校验

每个 API 调用者会被分配到一组配对的 key-secret , key 用于标识调用者的身份, secret 用于生成签名。 在发起 HTTP 请求时,签名信息放在 Authorization 头,格式为:

Authorization: SLIM-AUTH Key={key}, Sign={sign}, Timestamp={timestamp}, Version=1

花括号内是可变的参数值。除开头的 scheme 部分外,其余各参数由逗号隔开,顺序不做要求,参数名称前的空白字符会被忽略。各参数定义为:

  • Authorization scheme 固定为 SLIM-AUTH 。
  • Key 是请求者的 key 。
  • Sign 是基于请求内容和 secret 生成的签名。详见签名算法节。
  • Timestamp 是生成签名时的 UNIX 时间戳,单位是秒。
  • Version 表示签名算法的版本,当前固定值为 1 。可省略,省略时默认为 1 。

API 服务器将根据签名算法,校验 Sign 的值是否正确,并要求 Timestamp 在允许的误差范围内(默认为 300 秒)。

特别的,当不方便定制请求头时,也可以将 Authorization 头的值,放在 URL 的 ~auth 参数上(记得 urlEncode )。 ~auth 参数不参与签名计算。如果同时提供参数和请求头,则只读取请求头。 此功能特别适用于 JSONP 请求( SlimAPI 的功能之一),因为其不能定制 HTTP 头。

签名算法

字符集统一使用 UTF-8 。签名使用 HMAC-SHA256 算法,通过 secret 对待签名串进行哈希计算得到。待签名串根据请求的内容生成,格式为:

TIMESTAMP
METHOD
PATH
QUERY_VALUES
BODY_VALUES (optional)
END  (constant)

每个部分间用换行符(\n)分割,各部分的值为:

  1. TIMESTAMP 是生成签名时的 UNIX 时间戳,需和 Authorization 头里的 Timestamp 参数值一样。
  2. METHOD 是 HTTP 请求的 METHOD ,如 GET/POST/PUT 。
  3. PATH 请求的路径,没有路径部分时,使用“/”。 比如请求地址是“http://temp.org/the/path/”则路径为“/the/path/”; 地址是“http://temp.org/”或“http://temp.org”,路径均为“/”。
  4. QUERY_VALUES 是 URL 的 query string 部分拼接后的值。 先按参数名称的 UTF-8 字节顺序升序,将参数排列好,需使用稳定的排序算法,这样若有同名参数,其顺序不会被打乱; 然后排序后的参数的值紧密拼接起来(无分隔符); 若一个参数没有值,如“?a=&b=2”或“?a&b=2”中的“a”,则用参数名称代替值拼入。 没有 query string 时,整个 QUERY 部分使用一个空字符串。
  5. BODY_VALUES 若是 application/x-www-form-urlencoded 请求,则处理方式同 QUERY 。 若是 application/json 请求,则为 JSON 原文,和 BODY 上送的一致,不做任何修改。 GET 请求时此部分省略(包含换行符均省略)。 不支持其他类型的请求。
  6. 最后一行固定是“END”三个字符,末尾没有空行。

注意:

  • UTF-8 字节顺序不是字典顺序,字节顺序下,英文大写字母在小写字母前面,比如 X 排序在 a 前面。
  • 如果在 URL 上使用 ~auth 参数,此参数不参与签名计算。

例子1 - 参数的排序规则

当前示例及其后的示例中,时间戳均固定为 1662439087 ,使用的 Key 的值为 my_key , secret 的值为 my_secret 。

待签名的请求为:

POST http://temp.org/my/path?a&c=3&b=2&z=4&X=%E4%B8%AD%E6%96%87&a=1&b=
Content-Type: application/x-www-form-urlencoded

p1=11&p3=33&p2=22

这是请求的 HTTP 报文的内容(除去 HTTP/1.1 部分)。

  • 请求的 QUERY 部分为 a&c=3&b=2&z=4&X=%E4%B8%AD%E6%96%87&a=1&b= ,参数使用百分号转义(Percent-encoding)过, X 参数原始值为“中文”。
  • 请求的 BODY 部分是 p1=11&p3=33&p2=22 。

获取待签名串的步骤:

  1. 拼接 TIMESTAMP ,值为 1662439087 。
  2. 拼接 METHOD ,值为 POST 。
  3. 拼接 PATH ,即 http://temp.org/my/path 中的 /my/path 。
  4. 计算并追加 QUERY 部分。见下文描述。
  5. 计算并追加 BODY 部分。由于是 application/x-www-form-urlencoded 的请求, BODY 部分的处理和 QUERY 规则一样,结果为: "112233" 。
  6. 追加最后一行,固定值为 END 。

QUERY 部分的计算步骤为:

  1. 得到参数表 [a, c, b, z, X, a, b] ,将参数根据名称按 UTF-8 字节顺序升序排列,并且使用稳定排序算法。 排列后为 [X, a, a, b, b, c, z] ,其中两个“a”参数和“b”参数的顺序需和 URL 中出现的顺序一致。
  2. 按排序后的参数顺序,得到参数的原始值为:[中文, , 1, 2, , 3, 4] ,其中有两个空白值( X 和 b 参数),对应没有值的第一个“a”参数和第二个“b”参数。
  3. 按顺序将值拼接起来,若参数没有值,则使用参数名称替代,得到: "中文a12b34" 。

最终待签名串为:

1662439087
POST
/my/path
中文a12b34
112233
END

通过 my_secret 计算 HMAC-SHA256 值为: b3baa63839877585cc05495810fb10267317df2fceda2eddcb92a740f78d1ba5

拼接得到 Authorization 头,追加到请求头,最终请求为:

POST http://temp.org/my/path?a&c=3&b=2&z=4&X=%E4%B8%AD%E6%96%87&a=1&b=
Content-Type: application/x-www-form-urlencoded
Authorization: SLIM-AUTH Key=my_key, Sign=b3baa63839877585cc05495810fb10267317df2fceda2eddcb92a740f78d1ba5, Timestamp=1662439087, Version=1

p1=11&p3=33&p2=22

例子2 - 空白请求

GET http://temp.org

待签名串为:

1662439087
GET
/

END

由于是 GET 请求,待签名串由5部分构成,没有 BODY 部分;同时此请求没有参数,故 QUERY 部分为空字符串。 最终请求为:

GET http://temp.org
Authorization: SLIM-AUTH Key=my_key, Sign=980b8715cefc0b98ae2b0788ce849308757554fbe685a05a43e6bc31fb0d0a4c, Timestamp=1662439087, Version=1

例子3 - 使用 JSON 请求

POST http://temp.org/p/?x=1&y=2
Content-Type: application/json

{"key":"value"}

JSON 请求的 BODY 部分不用额外处理,直接原样拼接到待待签名串。注意:如果 JSON 中本身有换行,也原样拼接,不需要额外处理。

得到待签名串为:

1662439087
POST
/p/
12
{"key":"value"}
END

最终请求为:

POST http://temp.org/p/?x=1&y=2
Content-Type: application/json
Authorization: SLIM-AUTH Key=my_key, Sign=ce0906df79291d516bb443adbc6099b39f36c006696150202e4e41ffe7dab211, Timestamp=1662439087, Version=1

Index

Constants

View Source
const (
	// 默认的签名算法版本,当 Authorization 头没有写 Version 字段时,默认为此版本。
	DefaultSignVersion = 1

	// SlimAuth 协议在 HTTP Authorization 头的 <scheme> 部分,固定值。
	DefaultAuthScheme = "SLIM-AUTH"

	// HTTP 协议的 Authorization 头。
	HttpHeaderAuthorization = "Authorization"
)

Variables

View Source
var LogAuthorization = logAuthorization{}

LogAuthorization 实现 webapi.LogSetup ,用于记录请求的 Authorization 头的相关信息。

这是一个单例。

Functions

func BuildAuthorizationHeader

func BuildAuthorizationHeader(auth Authorization) string

BuildAuthorizationHeader 返回用于 HTTP 的 Authorization 头的值。

  • 若 [Authorization.Version] 为 0 ,则 Version 部分被省略。
  • 若 [Authorization.AuthScheme] 为空,则使用默认值 DefaultAuthScheme

func HmacSha256

func HmacSha256(secret, data []byte) string

HmacSha256 计算 hmac-sha256 ,返回小写的 HEX 格式。

func NewSlimAuthApiDecoder

func NewSlimAuthApiDecoder() webapi.ApiDecoder

NewSlimAuthApiDecoder 返回 SlimAuth 协议的 webapi.ApiDecoder

func NewSlimAuthApiHandler

func NewSlimAuthApiHandler(op SlimAuthApiHandlerOption) *webapi.ApiHandlerWrapper

NewSlimAuthApiHandler 创建 SlimAuth 协议的 webapi.ApiHandler

func NewSlimAuthApiLogger

func NewSlimAuthApiLogger() webapi.LogSetupPipeline

NewSlimAuthApiLogger 返回用于 SlimAuth 协议的 webapi.ApiLogger 实现。

func NewSlimAuthApiNameResolver

func NewSlimAuthApiNameResolver(authScheme string, secretFinder SecretFinderFunc, timeChecker TimeCheckerFunc) webapi.ApiNameResolver

NewSlimAuthApiNameResolver 返回 SlimAuth 协议的 webapi.ApiNameResolver 。 它增加了签名校验,其余都和 SlimAPI 一样。

func SetBufferedAuthorization

func SetBufferedAuthorization(state *webapi.ApiState, auth Authorization)

缓存解析到的 Authorization

Types

type Authorization

type Authorization struct {
	AuthScheme string // Authorization 头最前面的 Scheme 部分。
	Key        string // 请求方的标识。
	Sign       string // 签名。
	Timestamp  int64  // 生成签名时的 UNIX 时间戳,单位是秒。
	Version    int    // 算法版本。在 Authorization 头未给出时,默认为 [DefaultSignVersion] 。
}

Authorization 记录 SlimAuth 协议规定的 HTTP Authorization 头的内容。

func GetBufferedAuthorization

func GetBufferedAuthorization(state *webapi.ApiState) (auth Authorization, ok bool)

获取当前请求中缓存的 Authorization 。若值不存在,返回默认值及 ok=false 。 在 webapi.ApiNameResolver.FillMethod 发生后,被解析到的 Authorization 将被缓存。

func MustGetBufferedAuthorization

func MustGetBufferedAuthorization(state *webapi.ApiState) Authorization

获取当前请求中缓存的 Authorization 。若值不存在, panic 。 在 webapi.ApiNameResolver.FillMethod 发生后,被解析到的 Authorization 将被缓存。

func ParseAuthorizationHeader

func ParseAuthorizationHeader(r *http.Request, authScheme string) (Authorization, error)

ParseAuthorizationHeader 解析 Authorization 头。当没有 Authorization 头时,尝试读取 URL 上的 ~auth 参数。

格式为:

Authorization: Scheme Key=value_of_key, Sign=value_of_sign, Timestamp=unix_timestamp, Version=1

说明:

  • 每个 Key 前的空格被忽略。 key-value 对的顺序不做要求。
  • Scheme 必须是匹配给定的 @authScheme ,若给定值为空,则使用默认值“SLIM-AUTH”。
  • Timestamp 签名时的 UNIX 时间戳,单位是秒。
  • Version 可省略,省略时默认为 1 。

type SecretFinderFunc

type SecretFinderFunc func(accessKey string) string

SecretFinderFunc 用于获取绑定到指定 accessKey 的 secret 。 若给定的 accessKey 没有绑定,返回空字符串。 若获取过程出错,直接 panic ,其错误处理方式与普通的 API 方法一致。

type SignResult

type SignResult struct {
	Sign  string         // 签名成功时,为签名的值。
	Type  SignResultType // 签名结果。
	Cause error          // 签名失败时,记录原因。
}

表示签名执行的结果和错误原因(当有错误时)。

func AppendSign

func AppendSign(r *http.Request, accessKey, secret string, authScheme string, timestamp int64) SignResult

AppendSign 计算请求的签名,并将其赋值到请求的 Authorization 头。 调用此方法后, http.Request.Body 会被读取并重新置换为新的 bytes.Buffer ,旧的 body 会被 Close 。

  • r 需要计算签名的请求。
  • accessKey 对应 Authorization 头中的 Key 字段的值。
  • secret HMAC-SHA256 的密钥,使用 UTF-8 字符集。
  • authScheme Authorization 头最前面的 Scheme 部分。为空时自动使用 DefaultAuthScheme
  • timestamp UNIX 时间戳,对应 Authorization 头的 Timestamp 字段的值。

func Sign

func Sign(r *http.Request, rewindBody bool, secret string, timestamp int64) SignResult

Sign 计算给定的请求的签名。

  • r 需要计算签名的请求。。
  • rewindBody 指定是否需要重用 http.Request.Body 。 若为 true ,则读取完 body 后,它会被替换为新的、可重读的 bytes.Buffer ,旧的 body 会被 Close 。
  • secret HMAC-SHA256 的密钥,使用 UTF-8 字符集。
  • timestamp UNIX 时间戳,对应 Authorization 头的 Timestamp 字段的值。

type SignResultType

type SignResultType int

签名结果。

const (
	SignResultType_OK                     SignResultType = iota // 签名成功。
	SignResultType_MissingContentType                           // 当 POST 请求缺少 Content-Type 头时给定此错误。
	SignResultType_UnsupportedContentType                       // 当有 POST 请求有 Content-Type 头,但类型不受支持时给定此错误。
	SignResultType_InvalidRequestBody                           // 请求的 body 部分缺失或格式错误。
)

type SlimAuthApiHandlerOption

type SlimAuthApiHandlerOption struct {
	// 名称。
	Name string

	// 指定 HTTP Authorization 头的 scheme 部分的值。
	// 若为空,则自动使用默认值 [DefaultAuthScheme] 。
	AuthScheme string

	// 用于查找签名所需的 secret 。必须提供。
	SecretFinder SecretFinderFunc

	// 用于校验签名信息中携带的时间戳的有效性。
	// 若为 nil ,将自动使用 [DefaultTimeChecker] ;若不需要校验,可给定 [NoTimeChecker] 。
	TimeChecker TimeCheckerFunc
}

SlimAuthApiHandlerOption 用于初始化 SlimAuth 协议的 webapi.ApiHandler

type SlimAuthInvoker added in v0.6.8

type SlimAuthInvoker[TParam, TData any] struct {
	*slimapi.SlimApiInvoker[TParam, TData]
}

SlimAuthInvoker 用于调用一个 SlimAuth 协议的 API 。

TParam 是输入参数的类型; TData 对应输出的 webapi.ApiResponse.Data

func NewSlimAuthInvoker added in v0.6.8

func NewSlimAuthInvoker[TParam, TData any](op SlimAuthInvokerOp) *SlimAuthInvoker[TParam, TData]

SlimAuthInvoker 创建一个 SlimAuthInvoker 实例。

type SlimAuthInvokerOp added in v0.6.8

type SlimAuthInvokerOp struct {
	Uri        string // 目标 URL 。
	Key        string // SlimAuth 协议的 key 。
	Secret     string // SlimAuth 协议的 secret 。
	AuthScheme string // Authorization 头的 <scheme> 部分,为空时自动使用默认值。
}

SlimAuthInvokerOp 用于初始化 SlimAuthInvoker

type TimeCheckerFunc

type TimeCheckerFunc func(timestamp int64) error

TimeCheckerFunc 用于校验签名信息中携带的时间戳的有效性。 若时间校验不通过,返回相关描述信息;否则返回 nil 表示校验通过。 若方法 panic ,其错误处理方式与普通的 API 方法一致。

var (
	// 不校验时间戳的 [TimeCheckerFunc] 。
	NoTimeChecker TimeCheckerFunc = func(timestamp int64) error {
		return nil
	}

	// 默认的时间戳校验 [TimeCheckerFunc] :要求签名给定的时间戳与当前时间误差在 5 分钟内。
	DefaultTimeChecker TimeCheckerFunc = MaxDeviationTimeChecker(300)
)

func MaxDeviationTimeChecker

func MaxDeviationTimeChecker(maxDeviation int64) TimeCheckerFunc

MaxDeviationTimeChecker 返回一个 TimeCheckerFunc , 其校验给定的时间戳与当前时间的误差必须小于等于 maxDeviation ,单位为秒。 maxDeviation 应为非负数,否则校验总是通过。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL