iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 10
0
Modern Web

BeeGo系列 第 10

Form and CSRF(2)

Customize Form

上篇提到 renderform 不能處理日期時間欄位的事情,今天再次花了點時間去追蹤 renderForm 的程式,然後動手做了點實驗。

renderForm 會先確定傳進來的參數是個 struct pointer,然後用 reflect 去依序分析每個欄位。主要把日期時間欄位篩選掉的部份是 unKind 這個 map。unKind 表明要把 pointer, array, slice, map, func, complex, struct 等類型的欄位篩選掉,time.Time 也算在這裏面,所以看來並不是我使用方法錯誤。

因此,先改寫原本的 views/user/create.tpl

{{ template "base.tpl" . }}

{{ define "content" }}
  <h1>User - Create</h1>
    {{if eq true .has_error}}
    <div class="alert alert-danger" role="alert">
    Error: {{.error}}
    </div>
    {{end}}
  <form action="" method="POST">
    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" class="form-control" id="name" placeholder="Your name">
    </div>
    <div class="form-group">
      <label for="gender">Gender</label>
      <input type="text" class="form-control" id="gender" placeholder="Gender">
    </div>
    <div class="form-group">
      <label for="birthday">Birthday</label>
      <input type="date" class="form-control" id="birthday" placeholder="Birthday">
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
  </form>
{{ end }}

然後在 controllers/myuser.go 裡撰寫 POST 處理函式。

// PostAddForm
func (c *MyUserController) PostAddForm() {
    c.TplName = "user/create.tpl"
    c.Data["has_error"] = false

    // parse form parameters
    var birthday time.Time
    name := c.GetString("name")
    gender := c.GetString("gender")
    t, err := time.Parse("2006-01-02", c.GetString("birthday"))
    if err != nil {
        c.Data["error"] = err.Error()
        c.Data["has_error"] = true
        birthday = time.Time{}
        return
    }
    birthday = t
    
    // process insert object
    var user models.User
    user.Name = name
    user.Gender = gender
    user.Birthday = birthday
    id, err := models.AddUser(&user)
    if err != nil {
        c.Data["error"] = err.Error()
        c.Data["has_error"] = true
        return
    }
    c.Data["id"] = id
    c.Ctx.Redirect(302, "/myuser/")
}

BeeGo 的 controller 裡有提供了 GetString, GetStrings, GetInt, GetInt8, GetUint8, GetInt16, GetUint16, GetInt32, GetUint32, GetInt64, GetUint64, GetBool, GetFloat, GetFile, GetFiles 等函式來取得 Form 內容。

這邊就使用 GetString 來一一取得,日期格式需要轉換,所以用 time.Parse 來轉換。

這邊要特別提一下 Go 的日期轉換,這跟 Python 有很大的不同,格式的描述是用類似這樣 "2016-01-02" 的字串來描述,有點不直覺,但是看這篇文章golang的时区和神奇的time.Parse裡說,這是內部故意這樣設計的,因為解析方便。

取得表單內容,填到 User 物件以後,就可以呼叫 models.AddUser() 來進行新增了。同樣也是需要檢查新增的結果是否成功。

現在已經有了 POST 處理函式,接著在 router 裡補上這部份:

beego.Router("/myuser/create", &controllers.MyUserController{}, "post:PostAddForm")

這樣就行了。

XSRF

CSRF 是一種網路攻擊的手法,這裡不多提,這裡只提 BeeGo 的處理機制。想了解詳細的原理,可以參考後面的參考資料。

要先做的是設定,設定有兩種,一種是全域的,一種是只在 Controller 裡設定。

全域的設定,是修改 conf/app.conf ,依照我們之前的修改,則是改 conf/base.conf 。

# 是否啟用 XSRF
EnableXSRF = true
# XSRFKey,這個請自訂,不要照抄
XSRFKey = xsrf_key_for_dmeo
# XSRF token 過期時間,單位是秒
XSRFExpire = 3600

controller 的設定,則是在 Controller 裡,新增 Prepare 函式,在裏面寫

func (a *MyUserController) Prepare() {
    a.EnableXSRF = false
}

填寫完以後,要改前面寫好的 Form HTML,在 下方加入

<form>
{{ .xsrfdata }}
<!-- 省略 -->
</form>

然後再修改 Controller,處理 GET 的函式負責填入 xsrf token,處理 POST 的函式是負責檢查 xsrf token 是否正確。

// 處理 GET 的函式
// GetAddForm ...
func (c *MyUserController) GetAddForm() {
    c.Data["xsrfdata"] = template.HTML(c.XSRFFormHTML())  // 加入 xsrfdata
    c.Data["has_error"] = false
    c.TplName = "user/create.tpl"
}

// 處理 POST 的函式
// PostAddForm
func (c *MyUserController) PostAddForm() {
    // 省略
    // Check XSRF first.                                                                 
    if !c.CheckXSRFCookie() {
        c.Data["error"] = "XSRF token missing or incorrect."
        c.Data["has_error"] = true
        return
    }
    // 省略
}

好了,現在在網址列輸入 http://localhost:8080/myuser/create/ 以後,就可以看到新增的表單,並且進行新增了。

參考資料


上一篇
Form and CSRF(1)
下一篇
Routing
系列文
BeeGo30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言