• Cookie使用详解


    Cookie使用详解

    理论知识

    前言

    HTTP Cookie(也叫Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储 cookie 并在下次同一服务器再发起请求时携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器——如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。

    Cookie 主要用于以下三个方面

    • 会话状态管理
      • 用户登录状态
      • 购物车
      • 游戏分数
    • 个性设置
      • 用户自定义设置
      • 主题
    • 浏览器行为跟踪
      • 跟踪分析用户行为

    Cookie 曾用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在出现了许多现代存储API。由于服务器指定Cookie后,浏览器的每次请求都会携带Cookie数据,会带来额外的性能开销。目前新型的浏览器API已经开始允许开发者直接将数据存储到本地,如使用localStoragesessionStorageindexedDB

    创建Cookie

    服务器收到HTTP请求后,服务器可以在相应标头里面添加一个或多个Set-Cookie选项。浏览器收到响应后通常会保存下Cookie,并将其放在HTTP Cookie标头内,向同一服务器发出请求时一起发送。可以指定域和路径设置额外的限制,以限制cookie发送的位置。

    简单的说就是后端服务器可以在响应标头里面加上Set-Cookie这个特殊字段以及其值,让浏览器保存下来。

    Set-CookieCookie标头

    服务器使用Set-Cookie响应头部向用户代理(一般指浏览器)发送Cookie信息。一个简单的Cookie可能像这样子:

    Set-Cookie: =
    
    • 1

    cookie 的属性介绍

    name 、value

    Cookie的内容信息,以 name=value的形式进行存储,区分大小写。

    domain

    domain属性指定浏览器发送HTTP请求时,哪些域名要附带这个Cookie。

    可以设置domain为当前服务器域名或者父域名.如果未设置,则该Cookie值只能在发向该域名的请求时被携带。如果设置了domain,则发向该域以及子域的请求都会带上该Cookie.由于历史原因,存在前缀点的domain,有前缀点表示允许子域请求携带该cookie,无前缀点则不允许。目前规范规定忽略前缀点。

    path

    path指定浏览器发出HTTP请求时,哪些路径要附带这个Cookie。如果配置的时候设置了path,那么在该path下的子路径都允许携带该cookie

    例如,设置Path=/docs,则以下地址都会匹配:

    • /docs
    • /docs/
    • /docs/Web

    但是这些请求路径不会匹配以下地址:

    • /
    • /docsets
    • /api/docs
    Expires 、Max-Age

    这两个字段用于定义Cookie的生命周期。Expires是一个绝对时间,如果没有设置这个属性或者设置为null,则表示这是一个Session Cookie.需要注意的是,浏览器是根据本地时间与Expires对此判断是否过期,而本地时间是可能变动的,所以无法保证Cookie一定会在服务器指定的时间过期。

    Max-Age是一个相对时间,是在Cookie失效之前需要经过的秒数。秒数为0或-1将会使Cookie直接过期。

    那么要是当ExpiresMax-Age同时被设置时,Max-Age的值将头衔生效。如果都没有指定,那这个Cookie就是Session Cookie,一旦用户关闭浏览器,这个Cookie就会被删除。有些浏览器提供了绘画回复功能,这种情况下即使关闭了浏览器,Session Cookie也会被保留下来,就好像浏览器从来没有关闭一样,这会导致Cookie的生命周期无限期延长。

    SameSite

    SameSite Cookie允许服务器要求某个cookie在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF).总共有三个值:Strict,Lax,None。现在默认为Lax,为None时必须具有Secure属性。

    Secure,HttpOnly

    Secure属性限制该Cookie只有在HTTPS协议下,才会被发送到服务器。若当前协议时HTTP,则浏览器会忽略服务器发来的Secure属性。

    HttpOnly属性指定该Cookie无法通过javaScript脚本获取到,防止恶意脚本的攻击。

    Cookie与跨域、安全

    在发送跨域AJAX请求时,Cookie默认不会被发送。如果需要允许跨域请求携带Cookie,需要在发送请求的时候设置withCredentials请求头,服务端需要在请求相应中配置CORS相关头部(Allow-cross-with-credentials:true)。

    特殊的情况是,如果AJAX请求配置了withCredentials,即使响应没有配置CORS响应标头而被浏览器拦截,响应中的set-cookie也会生效,可以成功种上Cookie

    知识点小结

    请求会携带Cookie的条件

    • domainpath属性与请求的域名路径匹配,Cookie才会被携带
    • 如果有Secure属性,请求协议必须时HTTPS
    • 跨域AJAX请求不会携带Cookie,如果需要携带需要配置withCredentials请求头以及CORS响应头。
    • 如果请求跨站了,还要判断SameSite属性是否满足发送条件。

    实践

    相关配置修改

    这里讲的环境为Windows系统下

    直接快捷键win + r,将C:\Windows\System32\drivers\etc\hosts输入其中。选择确定并选择用记事本打开。

    在其中的空白行输入:

    127.0.0.1	ywhabc.com
    
    • 1

    表示ywhabc.com映射了127.0.0.1.

    设置的这个域名,与后面代码中设置Cookie时的domain属性相关

    image-20221102093354950

    现在我们就可以在本机上通过访问ywhabc.com而达到访问127.0.0.1达到相同的效果。

    image-20221102100853066

    代码实践

    为了更好的理解,这个运行的机制。采用GO gin框架使用cookie写一个简单的模拟用户登录的功能。

    案例目标功能:

    • 未登录时,无法成功进行其它请求
    • 有登录功能
    • 有登出功能
    • 用户登录后半小时内无请求操作,取消其登录

    采用的工具:

    • go gin 框架
    • redis (基于docker创建)

    操作Redis部分代码

    这个之前一篇博客有记录过,不细讲。下面没有对错误抛出进行正确处理。

    package main
    
    import (
    	"context"
    	"fmt"
    	"sync"
    	"time"
    
    	"github.com/go-redis/redis/v8"
    )
    
    var (
    	ctx             = context.Background()
    	linkRedisMethod sync.Once
    	DbRedis         *redis.Client
    )
    
    func init() {
    	initRedis()
    }
    
    // 连接redis数据库
    // 且只会执行一次
    func initRedis() {
    	linkRedisMethod.Do(func() {
    		//连接数据库
    		DbRedis = redis.NewClient(&redis.Options{
    			Addr:     "127.0.0.1:6379", // 对应的ip以及端口号
    			Password: "",               // 数据库的密码
    			DB:       0,                // 数据库的编号,默认的话是0
    		})
    		// 连接测活
    		_, err := DbRedis.Ping(ctx).Result()
    		if err != nil {
    			panic(err)
    		}
    		fmt.Println("连接Redis成功")
    	})
    }
    
    // 登录成功时写入登录标记
    func InsertLoginFlag(key, value string, timeout time.Duration) {
    	err := DbRedis.Set(ctx, key, value, timeout).Err()
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    	fmt.Println("insert successfully")
    }
    
    // 检查用户是否登录
    func CheckLoginFlag(key string) int {
    	_, err := DbRedis.Get(ctx, key).Result()
    	if err == redis.Nil {
    		return 0
    	} else if err != nil {
    		return -1
    	}
    	return 1
    }
    
    // 用户登出时删除登录记录
    func DelLoginFlag(key string) {
    	err := DbRedis.Del(ctx, key)
    	if err != nil {
    		fmt.Println(err)
    	}
    }
    
    // 更新用户的登录状态
    func UpdateLoginFlag(key string) {
    	DbRedis.Expire(ctx, key, time.Minute*30)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    路由部分主体代码

    这部分代码主要是用了中间件对用户登录状态进行验证,阻止用户进行非法请求。同时登录、登出、登录成功的测试部分包括了对cookie的设置和验证。

    package main
    
    import (
    	"net/http"
    	"time"
    
    	"github.com/gin-gonic/gin"
    )
    
    // 检验用户是否登录的中中间件
    func authorVriFMiddle(c *gin.Context) {
    	if c.Request.URL.Path == "/api/login" {
    		c.Next()
    		return
    	}
    	cookieKey, err := c.Cookie(LOGINNAME)
    	// 若有cookie就去数据库中检查确认是否登录
    	loginState := CheckLoginFlag(cookieKey)
    
    	if err != nil || loginState == 0 || loginState == -1 {
    		c.JSON(http.StatusUnauthorized, gin.H{"message": "访问未授权"})
    		c.Abort()
    	} else {
    		// 如果收到已登录用户的请求,那么就更新已登录用户的登录状态
    		if c.Request.URL.Path != "/api/logout" {
    			UpdateLoginFlag(cookieKey)
    		}
    		c.Next()
    	}
    }
    
    // 用于检验未登录能否获得数据
    func testIsLogin(c *gin.Context) {
    	c.JSON(http.StatusOK, gin.H{
    		"msg": "Hello",
    	})
    }
    
    // 用户登录接口
    func UserLogin(c *gin.Context) {
    	data := make(map[string]interface{})
    	c.BindJSON(&data)
    	userName := data["username"].(string)
    	// password := data["password"].(string)
    	// Do something to check password
    	// 向数据库中写入已登录标志, 过期时间为半小时
    	InsertLoginFlag("login-"+userName, "ok", time.Minute*30)
    
    	c.SetCookie(LOGINNAME, "login-"+userName, 110000, "/api", ".ywhabc.com", false, false)
    	c.JSON(http.StatusOK, gin.H{
    		"msg": "登录成功",
    	})
    }
    
    // 用户登出接口
    func UserLogout(c *gin.Context) {
    	cookieKey, _ := c.Cookie(LOGINNAME)
    	DelLoginFlag(cookieKey)
    	c.SetCookie(LOGINNAME, "", -1, "/api", ".ywhabc.com", false, false)
    	c.JSON(http.StatusOK, gin.H{
    		"msg": "成功登出",
    	})
    }
    
    func main() {
    	router := gin.Default()
    	t := router.Group("/api")
    	t.Use(authorVriFMiddle)
    
    	t.GET("/test", testIsLogin)
    
    	t.POST("/login", UserLogin)
    
    	t.GET("/logout", UserLogout)
    
    	router.Run(":886")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    实验过程记录

    下面给出用Apifox模拟用户登录的截图记录(按操作顺序给出)

    非法请求数据

    image-20221102145223850

    用户登录

    image-20221102145438412

    登录后请求数据

    image-20221102145458620

    用户登出

    image-20221102145522107

    验证登出

    image-20221102145538045

    其它

    其实我们是可以看到,保存在浏览器上的Cookie的.例如Chorme浏览器。

    image-20221102145842455

    image-20221102150014934

    然后搜索一下

    image-20221102150057665

    小结

    本来是最近要做个cas登录项目的,里面用到了Cookie相关东西,所以先学习一下这个部分。

  • 相关阅读:
    学习Redis这一篇就够了
    TypeScript泛型
    国外创意二维码应用案例:澳大利亚宜家“这不是家”活动,呼吁人们关注和帮助因家庭暴力而无家可归的人!
    深入了解C++中各种不同意义的new和delete
    OpenGL 绘制文本(QPainter)
    汽车辅助系统
    UE4 回合游戏项目 22- 控制新角色
    异常点检测的应用场景与检测方法(含代码实操案例)
    Postgresql与执行计划相关的配置项
    golang-bufio 缓冲扫描
  • 原文地址:https://blog.csdn.net/qq_52785898/article/details/127651968