本文已参与「新人创作礼」活动,一起开启掘金创作之路。
简介
最近在使用Gin重构之前的SpringBoot后台,虽然现在使用JWT来做登录鉴权比较流行,但是为了保持接口的一致性还是选择使用session机制来做登录鉴权,在Gin中使用得比较多的session中间件是gin-contrib/sessions,项目的GitHub上有简单使用教程,这里记录一下对其的封装,能通过session获取到当前登录用户的信息。
需求
之前的SpringBoot项目大致可以看作是一个微型的Blog平台,有许多操作如更新、删除文章通常要鉴定权限,只有本人才能更新、删除,当时的设计是当用户登录时用seesion存储用户的基本信息(ID、Email等),之后调用更新、删除API时就只用传文章相关的参数,不需要传用户参数(用户参数可以通过header中的sessionId获取)。当时使用的Shiro来管理session,通过调用Shiro的SecurityUtils就能获取到session数据,由于有许多接口都可能要用到当前登录用户的信息,所以写了一个BaseController来封装通过session读取用户信息,之后需要用到这个功能的Controller就继承BaseController。
import org.apache.shiro.SecurityUtils;
public abstract class BaseController {
/**
* 返回当前登录用户信息,User是自定义的用户类,存储用户ID、Email等信息
* @return
*/
protected User getCurrentUser(){
// 调用Shiro相关接口即可,都不需要上下文参数
return (User) SecurityUtils.getSubject().getSession().getAttribute("currentUser");
}
/**
* 设置当前登录用户的信息
* @param user
*/
protected void setCurrentUser(User user){
SecurityUtils.getSubject().getSession().setAttribute("currentUser", user);
}
}
复制代码
在Gin中使用Session
gin-contrib/sessions官方给出的使用教程如下"secret"表示的是生成sessionID时的密钥,随便填个字符串就行;"mysession"表示的是返回给前端的sessionId名称,比如我填"SESSIONID"那么返回給前端的cookie数据就有SESSIONID这个字段
package main
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
store := cookie.NewStore([]byte("secret")) // 设置生成sessionId的密钥
// mysession是返回給前端的sessionId名
r.Use(sessions.Sessions("mysession", store))
r.GET("/hello", func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("hello") != "world" {
session.Set("hello", "world")
session.Save()
}
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
r.Run(":8000")
}
复制代码
通过Session中存取用户信息
官方只介绍了简单类型的数据存取,但是有些时候我们可能需要在session中存取结构体对象,比如我在上面需求中提到的要存取用户信息。这个时候就要注意,得先使用gob注册结构体,gob是golang自带得序列化编解码工具,在对自定义struct编解码时要调用gob.Register进行注册,否则在调用session.Save()时会返回以下错误,并且还不容易发现,当时在这里卡了好久。。。
securecookie: error - caused by: securecookie: error - caused by: gob: type not registered for interface: model.User
复制代码
在使用session读取User结构体时还要使用Golang的类型转换(断言),因为session.Get获取到是一个interface。再简单的编写一下路由,包含一个login登录接口,登录成功时将用户信息写入session;还有一个测试接口,不用传递参数返回当前登录用户的用户名。完整测试代码如下:
type User struct {
Id int `json:"id"`
Email string `json:"email"`
Username string `json:"username"`
Password string `json:"password"`
}
func getCurrentUser(c *gin.Context) (userInfo User) {
session := sessions.Default(c)
userInfo = session.Get("currentUser").(User) // 类型转换一下
return
}
func setCurrentUser(c *gin.Context, userInfo User) {
session := sessions.Default(c)
session.Set("currentUser", userInfo)
// 一定要Save否则不生效,若未使用gob注册User结构体,调用Save时会返回一个Error
session.Save()
}
func setupRouter(r *gin.Engine) {
r.POST("/login", func(c *gin.Context) {
var loginVo User
if c.ShouldBindJSON(&loginVo) != nil {
c.String(http.StatusOK, "参数错误")
return
}
if loginVo.Email == db.Email && loginVo.Password == db.Password {
setCurrentUser(c, *db) // 邮箱和密码正确则将当前用户信息写入session中
c.String(http.StatusOK, "登录成功")
} else {
c.String(http.StatusOK, "登录失败")
}
})
r.GET("/sayHello", func(c *gin.Context) {
userInfo := getCurrentUser(c)
c.String(http.StatusOK, "Hello "+userInfo.Name)
})
}
var db = &User{Id: 10001, Email: "[email protected]", Username: "Alice", Password: "123456"} // 不操作数据库,把所有用户信息写死在代码里
func main() {
gob.Register(User{}) // 注册User结构体
r := gin.Default()
store := cookie.NewStore([]byte("snaosnca"))
r.Use(sessions.Sessions("SESSIONID", store))
setupRouter(r)
r.Run(":8080")
}
复制代码
测试运行
完整可运行代码见Github go run ./main.go
再使用Postman发送登录请求,得到下图结果:
登录成功后再向/sayHello接口发送一个不带参数的get请求,得到下图结果,说明使用session成功存取了当前登录用户的信息。