Skip to content

Commit 70b0193

Browse files
committed
音视频链接加密播放
1 parent 051dd4c commit 70b0193

File tree

5 files changed

+130
-7
lines changed

5 files changed

+130
-7
lines changed

controllers/DocumentController.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,18 +292,22 @@ func (this *DocumentController) Read() {
292292
contentSelection.SetAttr("alt", doc.DocumentName+" - 图"+fmt.Sprint(i+1))
293293
}
294294
})
295+
295296
medias := []string{"video", "audio"}
296297
for _, item := range medias {
297298
query.Find(item).Each(func(idx int, sel *goquery.Selection) {
298299
title := strings.TrimSpace(sel.Text())
299300
poster, _ := sel.Attr("poster")
300301
src, _ := sel.Attr("src")
302+
sign, _ := utils.GenerateSign(src, time.Duration(utils.MediaDuration)*time.Second)
303+
src = src + "?sign=" + sign
301304
if item == "video" {
302305
sel.BeforeHtml(fmt.Sprintf(videoBoxFmt, title, poster, src, title))
303306
sel.Remove()
304307
} else {
305308
sel.SetAttr("preload", "true")
306309
sel.SetAttr("controlslist", "nodownload")
310+
sel.SetAttr("src", src)
307311
}
308312
})
309313
}

controllers/StaticController.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,21 @@ func (this *StaticController) APP() {
3838
func (this *StaticController) Uploads() {
3939
file := strings.TrimLeft(this.GetString(":splat"), "./")
4040
path := strings.ReplaceAll(filepath.Join("uploads", file), "\\", "/")
41-
fmt.Println("===========", path)
41+
42+
// 签名验证
43+
sign := this.GetString("sign")
44+
if !this.isValidSign(sign, path) {
45+
// 签名验证不通过,需要再次验证书籍是否是用户的(针对编辑状态)
46+
if !this.isBookOwner() {
47+
this.Abort("404")
48+
return
49+
}
50+
}
51+
52+
if utils.IsSignUsed(sign) {
53+
this.Abort("404")
54+
}
55+
4256
http.ServeFile(this.Ctx.ResponseWriter, this.Ctx.Request, path)
4357
}
4458

@@ -62,26 +76,29 @@ func (this *StaticController) ProjectsFile() {
6276

6377
object := filepath.Join("projects/", strings.TrimLeft(this.GetString(":splat"), "./"))
6478
object = strings.ReplaceAll(object, "\\", "/")
79+
6580
// 不是音频和视频,直接跳转
6681
if !this.isMedia(object) {
6782
this.Redirect(this.OssDomain+"/"+object, 302)
6883
return
6984
}
7085

71-
// query := this.Ctx.Request.URL.Query()
7286
// 签名验证
7387
sign := this.GetString("sign")
74-
if !this.isValidSign(sign) {
88+
if !this.isValidSign(sign, object) {
7589
// 签名验证不通过,需要再次验证书籍是否是用户的(针对编辑状态)
7690
if !this.isBookOwner() {
7791
this.Abort("404")
7892
return
7993
}
8094
}
8195

82-
var expireInSec int64 = 2
96+
if utils.IsSignUsed(sign) {
97+
this.Abort("404")
98+
}
99+
83100
if bucket, err := store.ModelStoreOss.GetBucket(); err == nil {
84-
object, _ = bucket.SignURL(object, http.MethodGet, expireInSec)
101+
object, _ = bucket.SignURL(object, http.MethodGet, utils.MediaDuration)
85102
if slice := strings.Split(object, "/"); len(slice) > 2 {
86103
object = strings.Join(slice[3:], "/")
87104
}
@@ -152,6 +169,10 @@ func (this *StaticController) isBookOwner() (yes bool) {
152169
}
153170

154171
// 是否是合法的签名(针对音频和视频,签名不可用的时候再验证用户有没有登录,用户登录了再验证用户是不是书籍所有人)
155-
func (this *StaticController) isValidSign(sign string) bool {
156-
return false
172+
func (this *StaticController) isValidSign(sign, path string) bool {
173+
signPath, err := utils.ParseSign(sign)
174+
if err != nil {
175+
return false
176+
}
177+
return signPath == path
157178
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/astaxie/beego v1.12.3
1818
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
1919
github.com/boombuler/barcode v1.0.0
20+
github.com/dgrijalva/jwt-go v3.2.0+incompatible
2021
github.com/disintegration/imaging v1.6.1
2122
github.com/go-sql-driver/mysql v1.5.0
2223
github.com/golang/protobuf v1.4.3 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGii
6868
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6969
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7070
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
71+
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
72+
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
7173
github.com/disintegration/imaging v1.6.1 h1:JnBbK6ECIZb1NsWIikP9pd8gIlTIRx7fuDNpU9fsxOE=
7274
github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
7375
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=

utils/jwt.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"sync"
7+
"time"
8+
9+
"github.com/astaxie/beego"
10+
"github.com/dgrijalva/jwt-go"
11+
)
12+
13+
// CustomClaims CustomClaims
14+
type CustomClaims struct {
15+
Path string
16+
jwt.StandardClaims
17+
}
18+
19+
var (
20+
jwtSecret = beego.AppConfig.DefaultString("jwt_secret", "bookstack.cn")
21+
usedSign sync.Map
22+
MediaDuration int64 = beego.AppConfig.DefaultInt64("media_duration", 3600*5)
23+
)
24+
25+
func init() {
26+
go func() {
27+
for {
28+
time.Sleep(600 * time.Second)
29+
clearExpireSign()
30+
}
31+
}()
32+
}
33+
34+
func clearExpireSign() {
35+
// 清除超过24小时
36+
usedSign.Range(func(signMD5, timestamp interface{}) bool {
37+
if time.Now().Unix()-timestamp.(int64) > MediaDuration {
38+
usedSign.Delete(signMD5)
39+
}
40+
return true
41+
})
42+
}
43+
44+
// IsSignUsed 签名是否已被使用
45+
func IsSignUsed(sign string) bool {
46+
signMD5 := MD5Sub16(sign)
47+
if _, ok := usedSign.Load(signMD5); ok {
48+
return true
49+
}
50+
usedSign.Store(signMD5, time.Now().Unix())
51+
return false
52+
}
53+
54+
// GenerateSign 生成token
55+
func GenerateSign(path string, expire ...time.Duration) (sign string, err error) {
56+
path = strings.TrimLeft(path, "/")
57+
// 默认过期时间为一个月
58+
expireDuration := time.Now().Add(30 * 24 * time.Hour)
59+
if len(expire) > 0 {
60+
expireDuration = time.Now().Add(expire[0])
61+
}
62+
63+
customClaims := &CustomClaims{
64+
path,
65+
jwt.StandardClaims{
66+
ExpiresAt: expireDuration.Unix(),
67+
},
68+
}
69+
70+
// 将 uid,过期时间作为数据写入 token 中
71+
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, customClaims)
72+
73+
// secret 用于对用户数据进行签名,不能暴露
74+
return jwtToken.SignedString([]byte(jwtSecret))
75+
}
76+
77+
// ParseSign 解析jwt token
78+
func ParseSign(sign string) (path string, err error) {
79+
var token = &jwt.Token{}
80+
token, err = jwt.ParseWithClaims(sign, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
81+
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
82+
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
83+
}
84+
return []byte(jwtSecret), nil
85+
})
86+
87+
if err != nil {
88+
return
89+
}
90+
91+
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
92+
return strings.TrimLeft(claims.Path, "/"), nil
93+
}
94+
return "", err
95+
}

0 commit comments

Comments
 (0)