完成了第四章的写作
This commit is contained in:
13
4.3.md
13
4.3.md
@@ -6,11 +6,12 @@
|
||||
|
||||
对XSS最佳的防护应该结合以下两种方法:验证所有输入数据,有效检测攻击(这个我们前面小节已经有过介绍);对所有输出数据进行适当的编码,以防止任何已成功注入的脚本在浏览器端运行。
|
||||
|
||||
那么Go里面是怎么做这个有效防护的呢?Go的html/template里面带有下面几个函数可以帮你转移
|
||||
那么Go里面是怎么做这个有效防护的呢?Go的html/template里面带有下面几个函数可以帮你转义
|
||||
|
||||
- func HTMLEscape(w io.Writer, b []byte) //把b进行转义之后写到w
|
||||
- func HTMLEscapeString(s string) string //转义s之后返回结果字符串
|
||||
- func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,返回结果字符串
|
||||
|
||||
- func HTMLEscape(w io.Writer, b []byte) //把需要转移的b进行转义之后写到w
|
||||
- func HTMLEscapeString(s string) string //转移s之后返回转义的字符串
|
||||
- func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,然后返回一个字符串
|
||||
|
||||
我们看4.1小节的例子
|
||||
|
||||
@@ -18,11 +19,11 @@
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
|
||||
|
||||
我们看到最后输出到客户端的代码如下:
|
||||
如果我们输入的username是`<script>alert()</script>`,那么我们可以在浏览器上面看到输出如下所示:
|
||||
|
||||

|
||||
|
||||
那么我们在输出我们的模板的时候怎么处理的呢?Go的html/template包现在默认就帮你过滤了html元素,但是有时候你又想输出这样的信息,text/template也支持。请看下面的例子所示
|
||||
那么我们在输出我们的模板的时候怎么处理的呢?Go的html/template包默认帮你过滤了html元素,但是有时候你又想输出这样的信息,请使用text/template。请看下面的例子所示
|
||||
|
||||
import "text/template"
|
||||
...
|
||||
|
||||
63
4.4.md
63
4.4.md
@@ -1,9 +1,56 @@
|
||||
#4.4防止多次递交表单
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [预防跨站脚本](<4.3.md>)
|
||||
* 下一节: [处理文件上传](<4.5.md>)
|
||||
|
||||
## LastModified
|
||||
#4.4防止多次递交表单
|
||||
|
||||
不知道你是否曾经看到过一个论坛或者博客,在一个帖子或者文章后面出现多条重复的记录,这些大多数是因为用户重复递交了留言的表单引起的。由于种种原因,用户经常会重复递交表单。通常这只是鼠标的误操作,如双击了递交按钮,也可能是为了编辑或者再次核对填写过的信息,点击了浏览器的后退按钮,然后又再次点击了递交按钮而不是浏览器的前进按钮。当然,也可能是故意的——比如,在某项在线调查或者博彩活动中重复投票。那我们如何有效的防止用户多次递交相同的表单呢?
|
||||
|
||||
解决方案是在表单中添加一个带有唯一值得隐藏字段。在验证表单时,先检查带有该惟一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮。
|
||||
|
||||
我继续拿4.2小节的例子优化:
|
||||
|
||||
用户名:<input type="text" name="username">
|
||||
密码:<input type="password" name="password">
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
<input type="submit" value="登陆">
|
||||
|
||||
我们看到在模版里面我们增加了一个隐藏字段`token`,这个值我们通过MD5(时间戳)来获取惟一值,然后我们把这个值存储到服务器端(session来控制,我们将在第六章讲解如何保存)
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
//请求的是登陆数据,那么执行登陆的逻辑判断
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if token != "" {
|
||||
//验证token的合法性
|
||||
} else {
|
||||
//不存在token报错
|
||||
}
|
||||
fmt.Println("username length:", len(r.Form["username"][0]))
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
|
||||
}
|
||||
}
|
||||
|
||||
上面的代码输出到页面的源码如下:
|
||||
|
||||

|
||||
|
||||
我们看到token已经有输出值,你可以不断的刷新,可以看到这个值在不断的变化。这样就保证了每次显示form表单的时候都是唯一的,用户递交的表单保持了唯一性。
|
||||
|
||||
我们的解决方案可以防止非恶意的攻击,并能使恶意用户暂时不知所措,然后,它却不能排除所有的欺骗性的动机,对此类情况还需要更复杂的工作。
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [预防跨站脚本](<4.3.md>)
|
||||
* 下一节: [处理文件上传](<4.5.md>)
|
||||
|
||||
## LastModified
|
||||
* $Id$
|
||||
87
4.5.md
Normal file
87
4.5.md
Normal file
@@ -0,0 +1,87 @@
|
||||
#4.5处理文件上传
|
||||
你想处理一个由用户上传的文件。例如,你正在建设一个类似Instagram 的网站,所以需要处理和存储用户提供的照片。我们该如何处理呢?
|
||||
|
||||
文件要能够上传,首先第一步就是要修改form的`enctype`属性,`enctype`属性有如下三种情况:
|
||||
|
||||
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
|
||||
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
|
||||
text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
|
||||
|
||||
我们如果要使得文件能够上传,那么我们的html应该如下所示
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>上传文件</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
|
||||
<input type="file" name="file" />
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
<input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
我们增加一个handlerFunc如下:
|
||||
|
||||
http.HandleFunc("/upload", upload)
|
||||
|
||||
// upload
|
||||
func upload(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) //获取请求的方法
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("upload.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
file, handler, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer file.Close()
|
||||
fmt.Fprintf(w, "%v", handler.Header)
|
||||
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, file)
|
||||
}
|
||||
}
|
||||
|
||||
通过上面的代码我们可以看到,文件上传我们需要调用`r.ParseMultipartForm`,里面的参数表示maxMemory,调用`ParseMultipartForm`之后我们上传的文件存储在设置的那么大的内存里面,如果文件大小超过了这个内存,那么剩下的部分存储在系统的临时文件中。我们可以通过`r.FormFile`获取上面的文件句柄,然后实例中使用了`io.Copy`来存储文件。
|
||||
|
||||
>获取其他非文件字段信息的时候就不需要调用`r.ParseForm`,因为在需要的时候Go自动会去调用。而且`ParseMultipartForm`调用一次之后,后面再次调用不会再有效果。
|
||||
|
||||
通过上面的实例我们可以看到我们上传文件主要三步处理:
|
||||
|
||||
- 1、表单中增加enctype="multipart/form-data"
|
||||
- 2、服务端调用`r.ParseMultipartForm`,把上传的文件存储在内存和临时文件中
|
||||
- 3、使用`r.FormFile`获取文件句柄,然后对文件进行存储等处理。
|
||||
|
||||
文件handler是multipart.FileHeader,里面存储了如下结构信息
|
||||
|
||||
type FileHeader struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
|
||||
我们通过上面的实例代码打印出来上传文件的信息如下
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [防止多次递交表单](<4.4.md>)
|
||||
* 下一节: [小结](<4.6.md>)
|
||||
|
||||
## LastModified
|
||||
* $Id$
|
||||
12
4.6.md
Normal file
12
4.6.md
Normal file
@@ -0,0 +1,12 @@
|
||||
#4.6 小结
|
||||
这一章里面我们学习了Go里面如何处理表单信息,我们通过一个登陆、一个上传例子展示了Go处理form表单信息,处理文件上传的能力。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全、数据过滤就变得相当重要了,因此专门一个小节讲解了各方面的数据过滤,顺带讲了一下Go里面对正则的处理。
|
||||
|
||||
通过这一章能够让你了解客户端和服务器端如何进行数据的交互,让客户端的数据进入我们的系服务器统,让我们系统处理之后的数据展现给客户端。
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [处理文件上传](<4.4.md>)
|
||||
* 下一章: [访问数据库](<5.md>)
|
||||
|
||||
## LastModified
|
||||
* $Id$
|
||||
BIN
images/4.4.token.png
Normal file
BIN
images/4.4.token.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
images/4.5.upload.png
Normal file
BIN
images/4.5.upload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
images/4.5.upload2.png
Normal file
BIN
images/4.5.upload2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
Reference in New Issue
Block a user