本篇主要讲解如何在Go中使用cookie做用户登录验证
cookie与token的区别
- token和cookie一样都是首次登陆时,由服务器下发,都是当交互时进行验证的功能,作用都是为无状态的HTTP提供的持久机制。
- token存在哪儿都行,localstorage或者cookie。
- cookie:服务员看你的身份证,给你一个编号,以后,进行任何操作,都出示编号后服务员去看查你是谁。
- token:直接给服务员看自己身份证
- token优势在于,token由于服务器端不存储会话,所以可扩展性强,token还可用于APP中。
使用cookie做登录验证
1. 生成一个自定义的cookie值
我这里使用的是用户id加时间戳的方式是生成一个不重复唯一的cookie值
func CreateCookie(userid uint) (cookieValue string, err error) {
//获取到当前时间戳,以秒为单位
timeunix := time.Now().Unix()
//cookie的过期时间
timeunix = timeunix + (24 * 7 * 3600)
var str = strconv.Itoa(int(userid)) + ":" + strconv.Itoa(int(timeunix))
key := []byte("2fa6c1e9")
//使用DES加密算法进行加密
strEncrypted, err := Encrypt(str, key)
if err != nil {
log.Fatal(err)
}
cookieValue = strEncrypted
return cookieValue, err
}
这里使用的是Go的DES对称加密是算法生成cookie
DES加密算法
DES是一种对称加密算法,又称为美国数据加密标准。DES加密时以64位分组对数据进行加密,加密和解密都使用的是同一个长度为64位的密钥,实际上只用到了其中的56位,密钥中的第8、16…64位用来作奇偶校验。DES有ECB(电子密码本)和CBC(加密块)等加密模式。
DES算法的安全性很高,目前除了穷举搜索破解外, 尚无更好的的办法来破解。其密钥长度越长,破解难度就越大。
填充和去填充函数。
下面是DES算法的使用
//解密
func Decrypt(decrypted string, key []byte) (string, error) {
src, err := hex.DecodeString(decrypted)
if err != nil {
return "", err
}
block, err := des.NewCipher(key)
if err != nil {
return "", err
}
out := make([]byte, len(src))
dst := out
bs := block.BlockSize()
if len(src)%bs != 0 {
return "", errors.New("crypto/cipher: input not full blocks")
}
for len(src) > 0 {
block.Decrypt(dst, src[:bs])
src = src[bs:]
dst = dst[bs:]
}
out = ZeroUnPadding(out)
return string(out), nil
}
//加密
func Encrypt(text string, key []byte) (string, error) {
src := []byte(text)
block, err := des.NewCipher(key)
if err != nil {
return "", err
}
bs := block.BlockSize()
src = ZeroPadding(src, bs)
if len(src)%bs != 0 {
return "", errors.New("Need a multiple of the blocksize")
}
out := make([]byte, len(src))
dst := out
for len(src) > 0 {
block.Encrypt(dst, src[:bs])
src = src[bs:]
dst = dst[bs:]
}
return hex.EncodeToString(out), nil
}
func ZeroPadding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{0}, padding)
return append(ciphertext, padtext...)
}
func ZeroUnPadding(origData []byte) []byte {
return bytes.TrimFunc(origData,
func(r rune) bool {
return r == rune(0)
})
}
2.Go使用cookie
在生成一个唯一的cookie值后,使用Go将这一个唯一的cookie值存入浏览器的cookie中
c.SetCookie("abc", "123", 24*7*3600, "/", "localhost:8081", false, true)
里面的参数
参数1:name,设置cookie的key
参数2:value,设置cookie的值
参数3:maxAge:设置cookie有效期
参数4:path:设置cookie的路径,一般设置根目录/代表当前域名下的所有页面都可以获取该cookie,例如设置为/users代表在/users这个路由下才可以访问这个cookie,像/article文章路由下的页面则无法访问这个cookie
参数5:domain:设置cookie域名,如下我设置域名为.go.com代表以.go.com为域名或者go.com的二级域名都可以访问这个cookie,像java.com这种域名就无法访问这个cookie,localhost也不可以访问这个cookie
参数6:secure,设置为true的时候只有https才可以访问,http不可以访问
参数7:httpOnly,此项为微软对cookie做的扩展,如果设置了httpOnly,则通过程序(js、applet等等)将无法读取到cookie的信息,防止XSS攻击
应用中间件
func SessionAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
// 获取客户端cookie并校验
//拿到请求头中的cookie
cookie := c.Request.Header.Get("Cookie")
//切分拿到的cookie
parts := strings.SplitN(cookie, "=", 2)
fmt.Println("cookie", parts[1])
//取出redis中对应的cookie
redisCookie, err := redis.GetRedisCookie(parts[1])
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": "查询失败"})
return
}
if len(cookie) != 0 {
if parts[1] == redisCookie {
c.Next()
return
}
}
// 返回错误
c.JSON(http.StatusUnauthorized, gin.H{"msg": "用户未登录"})
// 若验证不通过,不再调用后续的函数处理
c.Abort()
return
}
}
中间件的应用
之后就可以检测用户是否已经登录,并在浏览器中保存了一个带有用户信息并加密的可解析的cookie。