You need to enable JavaScript to run this app.
导航

URL 鉴权概述

最近更新时间2024.04.11 14:19:27

首次发布时间2022.02.09 10:17:37

本文档介绍如何在火山引擎内容分发网络中配置 URL 鉴权。

背景

内容分发网络提供了 "Referer 防盗链","Origin 防盗链" 和 "IP 黑白名单" 对用户请求进行过滤。但是在某些情况下,Referer, Origin 和 IP 可以被伪造,容易造成站点资源被恶意盗用。如果您对于站点内容的安全性有很高的要求,可以采用 URL 鉴权。

URL 鉴权工作原理

客户端在发送请求至服务端时,按照您设定的签名规则计算签名,并在请求中包含这个签名。服务端收到请求后,需要校验签名。只有在校验通过的情况下,才会响应客户端请求。

本文档通过 示例代码 演示了客户端签名逻辑的实现。同时也描述了如何在内容分发网络中配置请求的鉴权逻辑。

鉴权流程

  1. 您在内容分发网络控制台对加速域名配置 URL 鉴权的规则。规则包括鉴权算法,密钥和 URL 有效时长。
  2. 客户端发送带签名的请求到加速域名。
  3. 内容分发网络根据加速域名配置的 URL 鉴权规则,做以下验证:
    • 判断内容分发网络计算得到的签名和客户端请求中包含的签名是否一致。判断的逻辑如下:
          MD5 值转化成小写进行比较。原因是签名参数在比较时是大小写敏感的。
    • 判断请求是否过期。如果满足以下条件,则请求未过期:
          内容分发网络收到请求的时间 <= 请求中包含的时间戳 + 鉴权参数中配置的有效时间。
  4. 如果验证通过,内容分发网络响应请求。如果不通过,则拒绝请求,返回 403 响应状态码。

URL 鉴权能够有效防止源站内容被恶意用户盗刷。

说明

  • URL 鉴权在内容分发网络进行,源站无需改造。
  • 如果内容分发网络通过了验证,在回源请求中不会包含签名参数。
  • 开启 URL 鉴权后,客户端的请求都必须包含签名。否则请求会失败。

鉴权计算器

控制台提供了鉴权计算器,一个便利的小工具。在完成 URL 鉴权的配置后,您可以使用鉴权计算器来:

  • 生成鉴权 URL。该 URL 除了包含签名,也符合您指定的鉴权类型所定义的 URL 格式。通过发送一个包含该 URL 的请求,您可以验证您的 URL 鉴权配置是否符合预期。

  • 验证客户端生成的鉴权 URL 是否与鉴权计算器生成的相同。

  • 验证签名的过期时间是否符合预期。

使用鉴权计算器

  1. 上点击 鉴权计算器鉴权计算器 页面会自动获取加速域名的 URL 鉴权配置。如果您没有配置备密钥,鉴权计算器会自动创建一个供参考,您也可以删除该备密钥。如果您删除了备密钥,在生成的鉴权 URL 中,就不会包含备用鉴权 URL。

  2. 原始 URL 处,输入您的原始请求 URL。

  3. (可选)指定一个 开始时间。默认情况下,开始时间 就是当前时间,用于计算签名的过期时间。

  4. 点击 生成鉴权,然后查看生成的鉴权 URL 以及签名的过期时间。

前提条件

如果源站使用了 URL 鉴权,您必须关闭源站的 URL 鉴权,然后按照以下步骤对加速域名配置相同的 URL 鉴权逻辑。

操作步骤

  1. 登录 火山引擎内容分发网络控制台
  2. 在左侧导航栏,点击 域名管理
  3. 域名管理 页面,找到需要配置的域名,点击 管理
    页面上方的筛选条件和搜索框可以帮助您快速找到要配置的域名。
  4. 在域名页面上,点击 访问控制 页签。
  5. 在页面右上方,点击 编辑编辑
  6. URL 鉴权 下方,设置 状态开启
  7. 选择一个 URL 鉴权类型,并进行相应的配置。
  8. 在页面右上方,点击 提交编辑

客户端示例代码

在客户端,您可以参考以下示例代码实现签名规则的逻辑,用来计算签名。

说明

Python 示例代码要求 Python 3.0。

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"math/rand"
	"regexp"
	"strconv"
	"time"
)

func splitUrl(url string) []string {
	reg := regexp.MustCompile("^(http://|https://)?([^/?]+)(/[^?]*)?(\\?.*)?$")
	return reg.FindStringSubmatch(url)
}

func getMd5(text string) string {
	hashByte := md5.Sum([]byte(text))
	return hex.EncodeToString(hashByte[:])
}

func getRandomString(length int) string {
	b := make([]byte, length)
	rand.Read(b)
	return fmt.Sprintf("%x", b)[:length]
}

func GenTypeAUrl(url string, key string, signName string, uid string, ts int64) string {
	params := splitUrl(url)
	scheme, host, path, args := params[1], params[2], params[3], params[4]
	randstr := getRandomString(10)
	text := fmt.Sprintf("%s-%d-%s-%s-%s", path, ts, randstr, uid, key)
	hash := getMd5(text)
	authArg := fmt.Sprintf("%s=%d-%s-%s-%s", signName, ts, randstr, uid, hash)
	if Objects.equals(args, "") {
		return fmt.Sprintf("%s%s%s?%s", scheme, host, path, authArg)
	} else {
		return fmt.Sprintf("%s%s%s%s&%s", scheme, host, path, args, authArg)
	}
}

func GenTypeBUrl(url string, key string, ts int64) string {
	params := splitUrl(url)
	scheme, host, path, args := params[1], params[2], params[3], params[4]
	tsStr := time.Unix(ts, 0).Format("200601021504")
	text := fmt.Sprintf("%s%s%s", key, tsStr, path)
	hash := getMd5(text)
	return fmt.Sprintf("%s%s/%s/%s%s%s", scheme, host, tsStr, hash, path, args)
}

func GenTypeCUrl(url string, key string, ts int64) string {
	params := splitUrl(url)
	scheme, host, path, args := params[1], params[2], params[3], params[4]
	tsStr := strconv.FormatInt(ts, 16)
	text := fmt.Sprintf("%s%s%s", key, path, tsStr)
	hash := getMd5(text)
	return fmt.Sprintf("%s%s/%s/%s%s%s", scheme, host, hash, tsStr, path, args)
}

func GenTypeDUrl(url, key, signName, timeName string, ts int64, base int) string {
	params := splitUrl(url)
	scheme, host, path, args := params[1], params[2], params[3], params[4]
	tsStr := strconv.FormatInt(ts, base)
	text := fmt.Sprintf("%s%s%s", key, path, tsStr)
	hash := getMd5(text)
	authArg := fmt.Sprintf("%s=%s&%s=%s", signName, hash, timeName, tsStr)
	if Objects.equals(args, "") {
		return fmt.Sprintf("%s%s%s?%s", scheme, host, path, authArg)
	} else {
		return fmt.Sprintf("%s%s%s%s&%s", scheme, host, path, args, authArg)
	}
}

// GenTypeEUrl Genrate signed url by custom rule(eg.:key+domain+uri+timestamp)
func GenTypeEUrl(url, key, signName, tsName string, ts int64, base int) string {
	params := splitUrl(url)
	scheme, domain, uri, args := params[1], params[2], params[3], params[4]
	tsStr := strconv.FormatInt(ts, base)
	text := fmt.Sprintf("%s%s%s%s", key, domain, uri, tsStr)
	hash := getMd5(text)
	authArg := fmt.Sprintf("%s=%s&%s=%s", signName, hash, tsName, tsStr)
	if Objects.equals(args, "") {
		return fmt.Sprintf("%s%s%s?%s", scheme, domain, uri, authArg)
	} else {
		return fmt.Sprintf("%s%s%s%s&%s", scheme, domain, uri, args, authArg)
	}
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {
	var url = "http://www.test.com/a.txt?a=b&c=d"
	var primaryKey = "primary123456"
	var signName = "sign"
	var timeName = "t"
	var uid = "0"
	var ts = time.Now().Unix()

	typeAUrl := GenTypeAUrl(url, primaryKey, signName, uid, ts)
	typeBUrl := GenTypeBUrl(url, primaryKey, ts)
	typeCUrl := GenTypeCUrl(url, primaryKey, ts)
	typeDUrl := GenTypeDUrl(url, primaryKey, signName, timeName, ts, 10)
	typeEUrl := GenTypeEUrl(url, primaryKey, signName, timeName, ts, 10)

	fmt.Println("OriginUrl: ", url)
	fmt.Println("TypeA: ", typeAUrl)
	fmt.Println("TypeB: ", typeBUrl)
	fmt.Println("TypeC: ", typeCUrl)
	fmt.Println("TypeD: ", typeDUrl)
	fmt.Println("TypeE: ", typeEUrl)
}