基于cookie的用户登录状态管理

cookie是什么

先来花5分钟看完这篇文章:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies

看完上文,相信大家对cookie已经有了一个整体的概念,我再强调一下,cookie是一个客户端概念,它是存储在浏览器本地的一小段文本(通常由服务器来生成这段文本)。

cookie的作用

如上文所说,cookie有许多作用,如会话状态管理,个性化设置,浏览器行为跟踪,客户端数据的存储等等。本篇文章就来讲讲基于cookie的用户登录状态管理。

插一句哈,一般提到cookie,还会有一个叫session的家伙和它一起出现,下篇文章我会讲到它,以及两者的区别。

cookie的产生过程

基于cookie的用户登录状态管理_第1张图片

如上图所示,客户端携带账号和密码向服务器发起请求,服务器在校验通过后,通过HTTP Respose Header中的Set-Cookie头部,将一小段文本写入客户端浏览器,在以后的每个客户端HTTP Request Header的Cookie头部中会自动携带这段文本。

基于cookie的用户登录状态管理

下面我基于golang和gin框架(中间件使用比较舒服)来简单的实现一个基于cookie的用户登录状态管理demo

package main

import (
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/login", Login)
    
    // 需要登陆保护的
    auth := r.Group("")
    auth.Use(AuthRequired())
    {
        auth.GET("/me", UserInfo)
        auth.GET("/logout", Logout)
    }

    r.Run("localhost:9000")
}

// 登陆
func Login(c *gin.Context) {
    // 为了演示方便,我直接通过url明文传递账号密码,实际生产中应该用HTTP POST在body中传递
    userID := c.Query("user_id")
    password := c.Query("password")

    // 用户身份校验(查询数据库)
    if userID == "007" && password == "007" {
        // 生成cookie
        expiration := time.Now()
        expiration = expiration.AddDate(0, 0, 1)
        // 实际生产中我们可以加密userID
        cookie := http.Cookie{Name: "userID", Value: userID, Expires: expiration}
        http.SetCookie(c.Writer, &cookie)

        c.JSON(http.StatusOK, gin.H{"msg": "Hello " + userID})
        return
    }
    c.JSON(http.StatusBadRequest, gin.H{"msg": "账号或密码错误"})
}

// 检测是否登陆的中间件
func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        cookie, _ := c.Request.Cookie("userID")
        if cookie == nil {
            c.JSON(http.StatusUnauthorized, gin.H{"msg": "请先登陆"})
            c.Abort()
        }
        // 实际生产中应校验cookie是否合法
        c.Next()
    }
}

// 查看用户个人信息
func UserInfo(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"msg": "007的个人页面"})
}

// 退出登陆
func Logout(c *gin.Context) {
    // 设置cookie过期
    expiration := time.Now()
    expiration = expiration.AddDate(0, 0, -1)
    cookie := http.Cookie{Name: "userID", Value: "", Expires: expiration}
    http.SetCookie(c.Writer, &cookie)

    c.JSON(http.StatusOK, gin.H{"msg": "退出成功"})
}

我们来看具体的演示流程和效果:

基于cookie的用户登录状态管理_第2张图片

基于cookie的用户登录状态管理_第3张图片

基于cookie的用户登录状态管理_第4张图片

如下图所示,当我们退出后再去尝试访问个人页面时,会出现401没有权限的错误。

基于cookie的用户登录状态管理_第5张图片

上述例子的缺点

先来说说上面的demo存在的问题吧,我们的退出登录函数本质是设置了一个过期了的cookie来覆盖以前发送给用户的正常cookie。

但是,这儿存在着一个重大的安全问题。如果用户将之前未过期的正常cookie记录下来(即本例子中的userID=007),即使调用了我们的logout接口,只要用户自己手动输入之前未过期的正常cookie,也是可以通过服务器的验证。

而且,最重要的是,我们无法让其失效,因为cookie的过期删除机制是由浏览器来控制的,但是当用户记录了cookie中的哪段文本后,在cookie到期后,浏览器只能删除存在于浏览器中的cookie,对用户自己记录下来的cookie确无能为力,也就是说这段cookie永远有效。(后面我们会讲一种叫json web token的技术,可以做到让我们签发的凭证自带过期机制,而不依赖浏览器)

当然,有同学会说,我们可以在服务器存储一份有效的cookie列表,在用户退出登录后,从有效列表中删除对应的cookie,这种在服务端维护用户状态的机制本质是session的思想,我们后面会讲基于session的用户登录状态管理。

再来说说cookie别的缺点:

跨站请求伪造

当然这个我们通过设置cookie的属性为HttpOnly,来禁止JavaScript读取cookie值,可以起到一定的防护作用。

当然,cookie也是有优点的,我们把用户的登录状态保存在客户端,这样就不需要每一次去访问数据库来检测用户是否登录,减少了系统的IO开销。

最后

本文希望通过一个不是很完美的demo来讲述基于cookie的用户登录状态管理,下期我们来讲讲session。

你可能感兴趣的:(基于cookie的用户登录状态管理)