Implement Captcha with Etcd as a storage backend
masterTo use etcd as a distributed store for captcha answers, you can wrap the base64Captcha.DriverString in a custom struct. This allows you to persist captcha answers in etcd with a TTL (Time To Live), ensuring that answers expire automatically after a set duration.
Implementation Steps:
- Define a custom struct: Embed
*base64Captcha.DriverStringand include youretcdclient. - Constructor: Use
base64Captcha.NewDriverStringto initialize the base driver, then wrap it in your custom struct. - Generate with Persistence: In your generation method, call
GenerateIdQuestionAnswer()to get the ID and answer, then useetcd.Grantto create a lease andetcd.Putto store the answer associated with the captcha ID. - Verify against Etcd: In your verification method, retrieve the value from
etcdusing the captcha ID and compare it with the user's provided answer.
// CaptchaEtcd base64 captcha with etcd
type CaptchaEtcd struct {
*base64Captcha.DriverString
store *etcd.Client
}
// NewClientEtcd constructor
func NewClientEtcd(height, width int, store *etcd.Client) *CaptchaEtcd {
d := base64Captcha.NewDriverString(height, width, 0, 0, 4, "%#=qwe23456789rtyupasdfghjkzxcvbnm", &color.RGBA{0, 0, 0, 0}, []string{"wqy-microhei.ttc"})
cli := &CaptchaEtcd{store: store}
cli.DriverString = d
return cli
}
// GenerateIdAndImage creates image and stores answer in etcd
func (c *CaptchaEtcd) GenerateIdAndImage() (id, b64s, ans string, err error) {
id, content, answer := c.GenerateIdQuestionAnswer()
item, err := c.DrawCaptcha(content)
if err != nil {
return "", "", "", err
}
// expire in 120s
grantResp, err := c.store.Grant(context.TODO(), 120)
if err != nil {
return "", "", "", err
}
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
_, err = c.store.Put(ctx, captchaPrefix+id, answer, clientv3.WithLease(grantResp.ID))
cancel()
if err != nil {
return "", "", "", err
}
b64s = item.EncodeB64string()
return id, b64s, answer, nil
}
// Verify checks captcha answer against etcd
func (c *CaptchaEtcd) Verify(id, answer string) (match bool, err error) {
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
key := captchaPrefix + id
resp, err := c.store.Get(ctx, key)
cancel()
if err != nil {
return false, err
}
for _, ev := range resp.Kvs {
if string(ev.Value) == answer {
return true, nil
}
}
return false, nil
}