add captcha util
Showing
5 changed files
with
244 additions
and
13 deletions
| ... | @@ -3,7 +3,6 @@ package beego | ... | @@ -3,7 +3,6 @@ package beego |
| 3 | import ( | 3 | import ( |
| 4 | "bytes" | 4 | "bytes" |
| 5 | "crypto/hmac" | 5 | "crypto/hmac" |
| 6 | "crypto/rand" | ||
| 7 | "crypto/sha1" | 6 | "crypto/sha1" |
| 8 | "encoding/base64" | 7 | "encoding/base64" |
| 9 | "errors" | 8 | "errors" |
| ... | @@ -22,6 +21,7 @@ import ( | ... | @@ -22,6 +21,7 @@ import ( |
| 22 | 21 | ||
| 23 | "github.com/astaxie/beego/context" | 22 | "github.com/astaxie/beego/context" |
| 24 | "github.com/astaxie/beego/session" | 23 | "github.com/astaxie/beego/session" |
| 24 | "github.com/astaxie/beego/utils" | ||
| 25 | ) | 25 | ) |
| 26 | 26 | ||
| 27 | var ( | 27 | var ( |
| ... | @@ -455,7 +455,7 @@ func (c *Controller) XsrfToken() string { | ... | @@ -455,7 +455,7 @@ func (c *Controller) XsrfToken() string { |
| 455 | } else { | 455 | } else { |
| 456 | expire = int64(XSRFExpire) | 456 | expire = int64(XSRFExpire) |
| 457 | } | 457 | } |
| 458 | token = getRandomString(15) | 458 | token = string(utils.RandomCreateBytes(15)) |
| 459 | c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire) | 459 | c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire) |
| 460 | } | 460 | } |
| 461 | c._xsrf_token = token | 461 | c._xsrf_token = token |
| ... | @@ -492,14 +492,3 @@ func (c *Controller) XsrfFormHtml() string { | ... | @@ -492,14 +492,3 @@ func (c *Controller) XsrfFormHtml() string { |
| 492 | func (c *Controller) GetControllerAndAction() (controllerName, actionName string) { | 492 | func (c *Controller) GetControllerAndAction() (controllerName, actionName string) { |
| 493 | return c.controllerName, c.actionName | 493 | return c.controllerName, c.actionName |
| 494 | } | 494 | } |
| 495 | |||
| 496 | // getRandomString returns random string. | ||
| 497 | func getRandomString(n int) string { | ||
| 498 | const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | ||
| 499 | var bytes = make([]byte, n) | ||
| 500 | rand.Read(bytes) | ||
| 501 | for i, b := range bytes { | ||
| 502 | bytes[i] = alphanum[b%byte(len(alphanum))] | ||
| 503 | } | ||
| 504 | return string(bytes) | ||
| 505 | } | ... | ... |
utils/captcha/captcha.go
0 → 100644
| 1 | package captcha | ||
| 2 | |||
| 3 | // modifiy and integrated to Beego in one file from https://github.com/dchest/captcha | ||
| 4 | |||
| 5 | import ( | ||
| 6 | "fmt" | ||
| 7 | "html/template" | ||
| 8 | "net/http" | ||
| 9 | "path" | ||
| 10 | "strings" | ||
| 11 | |||
| 12 | "github.com/astaxie/beego" | ||
| 13 | "github.com/astaxie/beego/cache" | ||
| 14 | "github.com/astaxie/beego/context" | ||
| 15 | "github.com/astaxie/beego/utils" | ||
| 16 | ) | ||
| 17 | |||
| 18 | var ( | ||
| 19 | defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} | ||
| 20 | ) | ||
| 21 | |||
| 22 | const ( | ||
| 23 | challengeNums = 6 | ||
| 24 | expiration = 600 | ||
| 25 | fieldIdName = "captcha_id" | ||
| 26 | fieldCaptchaName = "captcha" | ||
| 27 | cachePrefix = "captcha_" | ||
| 28 | urlPrefix = "/captcha/" | ||
| 29 | ) | ||
| 30 | |||
| 31 | type Captcha struct { | ||
| 32 | store cache.Cache | ||
| 33 | urlPrefix string | ||
| 34 | FieldIdName string | ||
| 35 | FieldCaptchaName string | ||
| 36 | StdWidth int | ||
| 37 | StdHeight int | ||
| 38 | ChallengeNums int | ||
| 39 | Expiration int64 | ||
| 40 | CachePrefix string | ||
| 41 | } | ||
| 42 | |||
| 43 | func (c *Captcha) key(id string) string { | ||
| 44 | return c.CachePrefix + id | ||
| 45 | } | ||
| 46 | |||
| 47 | func (c *Captcha) genRandChars() []byte { | ||
| 48 | return utils.RandomCreateBytes(c.ChallengeNums, defaultChars...) | ||
| 49 | } | ||
| 50 | |||
| 51 | func (c *Captcha) Handler(ctx *context.Context) { | ||
| 52 | var chars []byte | ||
| 53 | |||
| 54 | id := path.Base(ctx.Request.RequestURI) | ||
| 55 | if i := strings.Index(id, "."); i != -1 { | ||
| 56 | id = id[:i] | ||
| 57 | } | ||
| 58 | |||
| 59 | key := c.key(id) | ||
| 60 | |||
| 61 | if v, ok := c.store.Get(key).([]byte); ok { | ||
| 62 | chars = v | ||
| 63 | } else { | ||
| 64 | ctx.Output.SetStatus(404) | ||
| 65 | ctx.WriteString("captcha not found") | ||
| 66 | return | ||
| 67 | } | ||
| 68 | |||
| 69 | // reload captcha | ||
| 70 | if len(ctx.Input.Query("reload")) > 0 { | ||
| 71 | chars = c.genRandChars() | ||
| 72 | if err := c.store.Put(key, chars, c.Expiration); err != nil { | ||
| 73 | ctx.Output.SetStatus(500) | ||
| 74 | ctx.WriteString("captcha reload error") | ||
| 75 | beego.Error("Reload Create Captcha Error:", err) | ||
| 76 | return | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | img := NewImage(chars, c.StdWidth, c.StdHeight) | ||
| 81 | if _, err := img.WriteTo(ctx.ResponseWriter); err != nil { | ||
| 82 | beego.Error("Write Captcha Image Error:", err) | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | func (c *Captcha) CreateCaptchaHtml() template.HTML { | ||
| 87 | value, err := c.CreateCaptcha() | ||
| 88 | if err != nil { | ||
| 89 | beego.Error("Create Captcha Error:", err) | ||
| 90 | return "" | ||
| 91 | } | ||
| 92 | |||
| 93 | // create html | ||
| 94 | return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`+ | ||
| 95 | `<a class="captcha" href="javascript:">`+ | ||
| 96 | `<img onclick="this.src=('%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s.png">`+ | ||
| 97 | `</a>`, c.FieldIdName, value, c.urlPrefix, value, c.urlPrefix, value)) | ||
| 98 | } | ||
| 99 | |||
| 100 | func (c *Captcha) CreateCaptcha() (string, error) { | ||
| 101 | // generate captcha id | ||
| 102 | id := string(utils.RandomCreateBytes(15)) | ||
| 103 | |||
| 104 | // get the captcha chars | ||
| 105 | chars := c.genRandChars() | ||
| 106 | |||
| 107 | // save to store | ||
| 108 | if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil { | ||
| 109 | return "", err | ||
| 110 | } | ||
| 111 | |||
| 112 | return id, nil | ||
| 113 | } | ||
| 114 | |||
| 115 | func (c *Captcha) VerifyReq(req *http.Request) bool { | ||
| 116 | req.ParseForm() | ||
| 117 | return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName)) | ||
| 118 | } | ||
| 119 | |||
| 120 | func (c *Captcha) Verify(id string, challenge string) (success bool) { | ||
| 121 | if len(challenge) == 0 || len(id) == 0 { | ||
| 122 | return | ||
| 123 | } | ||
| 124 | |||
| 125 | var chars []byte | ||
| 126 | |||
| 127 | key := c.key(id) | ||
| 128 | |||
| 129 | if v, ok := c.store.Get(key).([]byte); ok && len(v) == len(challenge) { | ||
| 130 | chars = v | ||
| 131 | } else { | ||
| 132 | return | ||
| 133 | } | ||
| 134 | |||
| 135 | defer func() { | ||
| 136 | // finally remove it | ||
| 137 | c.store.Delete(key) | ||
| 138 | }() | ||
| 139 | |||
| 140 | // verify challenge | ||
| 141 | for i, c := range chars { | ||
| 142 | if c != challenge[i]-48 { | ||
| 143 | return | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | return true | ||
| 148 | } | ||
| 149 | |||
| 150 | func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { | ||
| 151 | cpt := &Captcha{} | ||
| 152 | cpt.store = store | ||
| 153 | cpt.FieldIdName = fieldIdName | ||
| 154 | cpt.FieldCaptchaName = fieldCaptchaName | ||
| 155 | cpt.ChallengeNums = challengeNums | ||
| 156 | cpt.Expiration = expiration | ||
| 157 | cpt.CachePrefix = cachePrefix | ||
| 158 | cpt.StdWidth = stdWidth | ||
| 159 | cpt.StdHeight = stdHeight | ||
| 160 | |||
| 161 | if len(urlPrefix) == 0 { | ||
| 162 | urlPrefix = urlPrefix | ||
| 163 | } | ||
| 164 | |||
| 165 | if urlPrefix[len(urlPrefix)-1] != '/' { | ||
| 166 | urlPrefix += "/" | ||
| 167 | } | ||
| 168 | |||
| 169 | cpt.urlPrefix = urlPrefix | ||
| 170 | |||
| 171 | return cpt | ||
| 172 | } | ||
| 173 | |||
| 174 | func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha { | ||
| 175 | cpt := NewCaptcha(urlPrefix, store) | ||
| 176 | |||
| 177 | // create filter for serve captcha image | ||
| 178 | beego.AddFilter(urlPrefix+":", "BeforeRouter", cpt.Handler) | ||
| 179 | |||
| 180 | // add to template func map | ||
| 181 | beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHtml) | ||
| 182 | |||
| 183 | return cpt | ||
| 184 | } |
utils/captcha/image.go
0 → 100644
This diff is collapsed.
Click to expand it.
utils/captcha/image_test.go
0 → 100644
| 1 | package captcha | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "testing" | ||
| 5 | |||
| 6 | "github.com/astaxie/beego/utils" | ||
| 7 | ) | ||
| 8 | |||
| 9 | type byteCounter struct { | ||
| 10 | n int64 | ||
| 11 | } | ||
| 12 | |||
| 13 | func (bc *byteCounter) Write(b []byte) (int, error) { | ||
| 14 | bc.n += int64(len(b)) | ||
| 15 | return len(b), nil | ||
| 16 | } | ||
| 17 | |||
| 18 | func BenchmarkNewImage(b *testing.B) { | ||
| 19 | b.StopTimer() | ||
| 20 | d := utils.RandomCreateBytes(challengeNums, defaultChars...) | ||
| 21 | b.StartTimer() | ||
| 22 | for i := 0; i < b.N; i++ { | ||
| 23 | NewImage(d, stdWidth, stdHeight) | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | func BenchmarkImageWriteTo(b *testing.B) { | ||
| 28 | b.StopTimer() | ||
| 29 | d := utils.RandomCreateBytes(challengeNums, defaultChars...) | ||
| 30 | b.StartTimer() | ||
| 31 | counter := &byteCounter{} | ||
| 32 | for i := 0; i < b.N; i++ { | ||
| 33 | img := NewImage(d, stdWidth, stdHeight) | ||
| 34 | img.WriteTo(counter) | ||
| 35 | b.SetBytes(counter.n) | ||
| 36 | counter.n = 0 | ||
| 37 | } | ||
| 38 | } |
utils/rand.go
0 → 100644
| 1 | package utils | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "crypto/rand" | ||
| 5 | ) | ||
| 6 | |||
| 7 | // RandomCreateBytes generate random []byte by specify chars. | ||
| 8 | func RandomCreateBytes(n int, alphabets ...byte) []byte { | ||
| 9 | const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | ||
| 10 | var bytes = make([]byte, n) | ||
| 11 | rand.Read(bytes) | ||
| 12 | for i, b := range bytes { | ||
| 13 | if len(alphabets) == 0 { | ||
| 14 | bytes[i] = alphanum[b%byte(len(alphanum))] | ||
| 15 | } else { | ||
| 16 | bytes[i] = alphabets[b%byte(len(alphabets))] | ||
| 17 | } | ||
| 18 | } | ||
| 19 | return bytes | ||
| 20 | } |
-
Please register or sign in to post a comment