说明
项目中用到了session鉴权,为此记录
为什么要使用session鉴权
首先要了解HTTP的无状态协议
。比较专业
的说法是因为它的每个请求都是完全独立的,每个请求包含了处理这个请求所需的完整的数据,发送请求不涉及到状态变更。通俗
的说法是服务器不记得你是谁,也就是说你的下一次请求必须要经历重新认证的过程(类似用户名密码登录)。作为用户来说,肯定是不希望这样的。因此,我们可以使用session鉴权来解决这个问题。
优点:HTTP的无状态协议,使用元数据(如Cookies头)来维护会话,使得会话与连接本身独立起来,即使连接断开了,会话状态也不会受到严重伤害,保持会话也不需要保持连接本身。此外,无状态的优点还在于对中间件友好,中间件无需完全理解通信双方的交互过程,只需要能正确分片消息即可,而且中间件可以很方便地将消息在不同的连接上传输而不影响正确性,这就方便了负载均衡等组件的设计。
缺点:单个请求需要的所有信息都必须要包含在请求中一次发送到服务端,这就导致单个消息的结构往往比较复杂,必须能够支持大量元数据。同时,这也导致了相同的数据在多个请求上往往需要反复传输,这在一定程度上降低了效率。
session鉴权
- 用户登陆成功后,将所需信息保存在session对象中,保存在数据库里
- 服务器生成一个Cookie,给用户颁发一个唯一的sessionid,并通过http的响应头返回,即Set-Cookie。
- 浏览器接收到用户的请求后,会自动保存里面的Cookie
- 在用户后续的请求里,浏览器会自动在请求头带上Cookie,服务器获取Cookie中的sessionid,从而进行验证
- 若用户关闭浏览器,会话状态即消失
session和jwt的区别
主要区别
:session的Cookie是存在服务器端,而jwt的token则存在客户端
说明
:session鉴权,服务器端生成Cookie,并存在数据库中,将其返回给客户端,客户端接下来的请求中会带上这个Cookie,服务器将会获取Cookie中的sessionid,从而进行验证
jwt鉴权,服务器生成token,并返回给客户端,在后续的请求中,客户端需加上相应的请求头,服务器解密进行对比,从而进行验证
go语言,gin框架,redis实现session鉴权
不会一次性给出所有代码,由于是一个项目开发,因此要将各个功能的代码进行封装,来提高代码可读性和可维护性
配置
1 2 3 4 5 6 7 8 9 10 11
| session: name: driver: redis
redis: host: "127.0.0.1" port: 6379 db: 0 user: root pass:
|
读取配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| var Config = viper.New()
func init() { Config.SetConfigName("config") Config.SetConfigType("yaml") Config.AddConfigPath(".") Config.WatchConfig() err := Config.ReadInConfig() if err != nil { log.Fatal("Config not find", err) } }
|
具体配置
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
| type driver string
const ( Memory driver = "memory" Redis driver = "redis" )
var defaultName = ""
type sessionConfig struct { Driver string Name string }
func getConfig() sessionConfig {
wc := sessionConfig{} wc.Driver = string(Memory) if config.Config.IsSet("session.driver") { wc.Driver = strings.ToLower(config.Config.GetString("session.driver")) }
wc.Name = defaultName if config.Config.IsSet("session.name") { wc.Name = strings.ToLower(config.Config.GetString("session.name")) }
return wc }
func getRedisConfig() redisConfig { Info := redisConfig{ Host: "localhost", Port: "6379", DB: 0, Password: "", } if config.Config.IsSet("redis.host") { Info.Host = config.Config.GetString("redis.host") } if config.Config.IsSet("redis.port") { Info.Port = config.Config.GetString("redis.port") } if config.Config.IsSet("redis.db") { Info.DB = config.Config.GetInt("redis.db") } if config.Config.IsSet("redis.password") { Info.DB = config.Config.GetInt("redis.password") } return Info }
|
创建Redis客户端(以便后续对Redis操作)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| type redisConfig struct { Host string Port string DB int Password string }
var RedisClient *redis.Client var RedisInfo redisConfig
func init() { info := getConfig()
RedisClient = redis.NewClient(&redis.Options{ Addr: info.Host + ":" + info.Port, Password: info.Password, DB: info.DB, }) RedisInfo = info }
|
创建基于 Redis 的会话存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| type redisConfig struct { Host string Port string DB int Password string }
func setRedis(r *gin.Engine, name string) { Info := getRedisConfig() store, _ := sessionRedis.NewStore(10, "tcp", Info.Host+":"+Info.Port, "", []byte("secret")) r.Use(sessions.Sessions(name, store)) }
|
选择会话的存储方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func Init(r *gin.Engine) { config := getConfig() switch config.Driver { case string(Redis): setRedis(r, config.Name) break case string(Memory): setMemory(r, config.Name) break default: log.Fatal("ConfigError") } }
|
使用
存
1 2 3 4 5 6 7 8 9 10 11
| func SetUserSession(c *gin.Context, user *models.User) error { webSession := sessions.Default(c) webSession.Options(sessions.Options{MaxAge: 3600 * 24 * 7, Path: "/api"}) webSession.Set("id", user.ID) return webSession.Save() }
|
用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func GetUserSession(c *gin.Context) (*models.User, error) { webSession := sessions.Default(c) id := webSession.Get("id") if id == nil { return nil, errors.New("") } user, _ := userServices.GetUserID(id.(int)) if user == nil { ClearUserSession(c) return nil, errors.New("") } return user, nil }
func ClearUserSession(c *gin.Context) { webSession := sessions.Default(c) webSession.Delete("id") webSession.Save() return }
|
更新
1 2 3 4 5 6 7 8 9 10 11
| func UpdateUserSession(c *gin.Context) (*models.User, error) { user, err := GetUserSession(c) if err != nil { return nil, err } err = SetUserSession(c, user) if err != nil { return nil, err } return user, nil }
|
额外知识:同源策略
浏览器的同源策略 - Web 安全 | MDN (mozilla.org)