登录页尝试使用csrf校验

This commit is contained in:
GoEdgeLab
2020-10-25 11:22:11 +08:00
parent 5345888280
commit 08af418665
11 changed files with 236 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
package configs
var Secret = ""

View File

@@ -0,0 +1,58 @@
package csrf
import (
"sync"
"time"
)
var sharedTokenManager = NewTokenManager()
func init() {
go func() {
ticker := time.NewTicker(1 * time.Hour)
for range ticker.C {
sharedTokenManager.Clean()
}
}()
}
type TokenManager struct {
tokenMap map[string]int64 // token => timestamp
locker sync.Mutex
}
func NewTokenManager() *TokenManager {
return &TokenManager{
tokenMap: map[string]int64{},
}
}
func (this *TokenManager) Put(token string) {
this.locker.Lock()
this.tokenMap[token] = time.Now().Unix()
this.locker.Unlock()
}
func (this *TokenManager) Exists(token string) bool {
this.locker.Lock()
_, ok := this.tokenMap[token]
this.locker.Unlock()
return ok
}
func (this *TokenManager) Delete(token string) {
this.locker.Lock()
delete(this.tokenMap, token)
this.locker.Unlock()
}
func (this *TokenManager) Clean() {
this.locker.Lock()
for token, timestamp := range this.tokenMap {
if time.Now().Unix()-timestamp > 3600 { // 删除一个小时前的
delete(this.tokenMap, token)
}
}
this.locker.Unlock()
}

66
internal/csrf/utils.go Normal file
View File

@@ -0,0 +1,66 @@
package csrf
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/iwind/TeaGo/types"
"strconv"
"time"
)
// 生成Token
func Generate() string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
h := sha256.New()
h.Write([]byte(configs.Secret))
h.Write([]byte(timestamp))
s := h.Sum(nil)
token := base64.StdEncoding.EncodeToString([]byte(timestamp + fmt.Sprintf("%x", s)))
sharedTokenManager.Put(token)
return token
}
// 校验Token
func Validate(token string) (b bool) {
if len(token) == 0 {
return
}
if !sharedTokenManager.Exists(token) {
return
}
defer func() {
sharedTokenManager.Delete(token)
}()
data, err := base64.StdEncoding.DecodeString(token)
if err != nil {
return
}
hashString := string(data)
if len(hashString) < 10+32 {
return
}
timestampString := hashString[:10]
hashString = hashString[10:]
h := sha256.New()
h.Write([]byte(configs.Secret))
h.Write([]byte(timestampString))
hashData := h.Sum(nil)
if hashString != fmt.Sprintf("%x", hashData) {
return
}
timestamp := types.Int64(timestampString)
if timestamp < time.Now().Unix()-1800 { // 有效期半个小时
return
}
return true
}

View File

@@ -1,6 +1,7 @@
package nodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/errors"
"github.com/iwind/TeaGo"
"github.com/iwind/TeaGo/Tea"
@@ -25,6 +26,7 @@ func NewAdminNode() *AdminNode {
func (this *AdminNode) Run() {
// 启动管理界面
secret := this.genSecret()
configs.Secret = secret
// 检查server配置
err := this.checkServer()

View File

@@ -0,0 +1,22 @@
package actionutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/csrf"
"github.com/iwind/TeaGo/actions"
"net/http"
)
type CSRF struct {
}
func (this *CSRF) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
action := actionPtr.Object()
token := action.ParamString("csrfToken")
if !csrf.Validate(token) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
action.WriteString("表单已失效,请刷新页面后重试(001)")
return
}
return true
}

View File

@@ -0,0 +1,12 @@
package csrf
import "github.com/iwind/TeaGo"
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Prefix("/csrf").
Get("/token", new(TokenAction)).
EndAll()
})
}

View File

@@ -0,0 +1,39 @@
package csrf
import (
"github.com/TeaOSLab/EdgeAdmin/internal/csrf"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"sync"
"time"
)
var lastTimestamp = int64(0)
var locker sync.Mutex
type TokenAction struct {
actionutils.ParentAction
}
func (this *TokenAction) Init() {
this.Nav("", "", "")
}
func (this *TokenAction) RunGet(params struct {
Auth *helpers.UserShouldAuth
}) {
locker.Lock()
defer locker.Unlock()
defer func() {
lastTimestamp = time.Now().Unix()
}()
// 没有登录,则限制请求速度
if params.Auth.AdminId() <= 0 && lastTimestamp > 0 && time.Now().Unix()-lastTimestamp <= 1 {
this.Fail("请求速度过快,请稍后刷新后重试")
}
this.Data["token"] = csrf.Generate()
this.Success()
}

View File

@@ -15,7 +15,9 @@ import (
"time"
)
type IndexAction actions.Action
type IndexAction struct {
actionutils.ParentAction
}
// 首页(登录页)
@@ -56,6 +58,7 @@ func (this *IndexAction) RunPost(params struct {
Remember bool
Must *actions.Must
Auth *helpers.UserShouldAuth
CSRF *actionutils.CSRF
}) {
params.Must.
Field("username", params.Username).

View File

@@ -7,6 +7,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/csrf"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/db"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns"

View File

@@ -0,0 +1,27 @@
Vue.component("csrf-token", {
created: function () {
this.refreshToken()
},
mounted: function () {
let that = this
this.$refs.token.form.addEventListener("submit", function () {
that.refreshToken()
})
},
data: function () {
return {
token: ""
}
},
methods: {
refreshToken: function () {
let that = this
Tea.action("/csrf/token")
.get()
.success(function (resp) {
that.token = resp.data.token
})
}
},
template: `<input type="hidden" name="csrfToken" :value="token" ref="token"/>`
})

View File

@@ -10,6 +10,7 @@
<script type="text/javascript" src="/js/md5.min.js"></script>
<script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
<script type="text/javascript" src="/ui/components.js?v=1.0.0"></script>
</head>
<body>
<div>
@@ -17,6 +18,7 @@
<div class="form-box">
<form class="ui form" data-tea-action="$" data-tea-before="submitBefore" data-tea-done="submitDone" data-tea-success="submitSuccess" autocomplete="off">
<csrf-token></csrf-token>
<input type="hidden" name="password" v-model="passwordMd5"/>
<input type="hidden" name="token" v-model="token"/>
<div class="ui segment stacked">