群里有人提到需求框架增加csrf token功能,于是基于新版中间件实现了一个简单版本,原理参照tp6。有需求可以在此基础上扩展

1. 中间件buildToken 在GET请求中创建token并存入session和赋值给模板

func csrfBuildTokenMiddleware(r *ghttp.Request) {
    if r.Method == "GET" {
        token, err := gmd5.Encrypt(r.EnterTime)
        if err != nil {
            r.ExitAll()
        }
        err = r.Session.Set("__token__", token)
        if err != nil {
            r.ExitAll()
        }
        g.View().Assign("token", token)
    }
    r.Middleware.Next()
}

2. 检测token合法性的中间件

func csrfCheckTokenMiddleware(r *ghttp.Request) {
    if !checkToken(r) {
        if r.IsAjaxRequest() {
            _ = r.Response.WriteJson(g.Map{"code": 40003, "msg": "invalid token", "data": nil})
            r.ExitAll()
        } else {
            r.Response.WriteStatus(http.StatusForbidden, "invalid token")
            r.ExitAll()
        }
    }
    r.Middleware.Next()
}

3. checkToken方法的实现。有需求的可以修改到自定义验证方法里面,验证可以更灵活,中间件检测相对简单粗暴,将需要检测的路由放进同样的路由分组,加上中间件即可。

func checkToken(r *ghttp.Request) bool {
    //忽略请求方式
    if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
        return true
    }
    //检测是否有令牌
    if !r.Session.Contains("__token__") {
        return false
    }
    //验证header
    if len(r.Header.Get("X-CSRF-TOKEN")) > 0 && r.Session.Get("__token__") == r.Header.Get("X-CSRF-TOKEN") {
        r.Session.Remove("__token__")
        return true
    }
    //验证表单token字段
    if len(r.GetString("token")) > 0 && r.Session.Get("__token__") == r.GetString("token") {
        r.Session.Remove("__token__")
        return true
    }
    r.Session.Remove("__token__")
    return false
}

示例:

    s := g.Server()
    
    s.BindMiddlewareDefault(csrfBuildTokenMiddleware)
    s.Group("/test", func(gr *ghttp.RouterGroup) {
        gr.Middleware(csrfCheckTokenMiddleware)
        gr.ALL("/test", func(r *ghttp.Request) {
            r.Response.WriteTpl("index.tpl", g.Map{
                "test": "test",
            })
        })
        gr.ALL("testpost", func(r *ghttp.Request) {
            r.Response.Write("testpost")
        })
    })
    s.BindHandler("/test1", func(r *ghttp.Request) {
        r.Response.Write("test1")
    })
        
    s.Run()



        //模板index.tpl代码 如果一切正常应该会跳转到testpost页面显示testpost
        <form action="testpost" method="post">
            <input type="text" name="token" value="${.token}"> //实际操作中将type设置为hidden
            <button type="submit">提交</button>
        </form>

       //也可以参照tp6在ajax请求中使用csrf token
       <meta name="csrf-token" content="${.token}">  //将token放到meta中
       
       $.ajaxSetup({
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            }
       });

goframe 项目地址 https://github.com/gogf/gf
goframe 文档地址 https://goframe.org
tp6 csrf 文档地址 https://www.kancloud.cn/manual/thinkphp6_0/1037632