Gin中的身份认证
# 一、Cookie
Cookie 是存储于访问者计算机的浏览器中,可以通过同一个浏览器访问同一个域名时共享数据。Cookie保存在客户浏览器中。
注:http是无状态协议。从一个页面转到另一个页面时,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次访问都是没有任何关系的。

cookie能实现的功能:
1、保持用户登录状态
2、保存用户浏览的历史纪录
3、猜你喜欢,智能推荐
4、电商网站的加入购物车等

# 方法定义
//main.go
func initWebServer() *gin.Engine {
server := gin.Default()
//注册中间件,作用于定义在server上的全部路由
//...
//定义cookie
store := cookie.NewStock([]byte("secret"))
server.Use(sessions.Sessions("weDemo", store))
return server
}
2
3
4
5
6
7
8
9
10
POST "/users/login"
login success
# 二、Session
Session是另一种记录客户状态的机制;Session保存在服务器上。
工作流程:
客户端浏览器第一次访问服务器并发送请求时,服务器会创建一个session对象,生成一个类似于key,value的键值对,将value保存到服务器,将key返回到浏览器(即客户端)。浏览器下次访问时会携带key(cookie),找到对应的的session(value)。
Gin官方:https://github.com/gin-contrib/sessions (opens new window)
# 方法定义
//web/user.go
func (u *UserHandler) Login(ctx *gin.Context) {
//一系列判定
//...
//登录成功,获取session
session := sessions.Default(ctx)
//设置session
session.Set("userId", user.Id)
//保存session
session.Save()
ctx.String(http.StatusOK, "login success")
}
2
3
4
5
6
7
8
9
10
11
12
POST "/users/login"
login success
# 三、Gin Sssion 存储实现
//main.go
func initWebServer() *gin.Engine {
server := gin.Default()
//注册中间件,作用于定义在server上的全部路由
//...
//内存memstore
store := memstore.NewStore([]byte("SPJbeQTIXdpJ7lSzidrOVoWsaEbLdZFB"), []byte("ueFnfGb0JFsvdzeH6ZIu6Oip2cEXVhIR"))
server.Use(sessions.Sessions("weDemo", store))
return server
}
2
3
4
5
6
7
8
9
10
注意:内存缓存是进程隔离的,所以不同的实例无法使用到相同的Session!
解决办法:负载均衡
# 三、JWT(Json Web Token)
原理:通过加密生成一个token,客户端每次都带上这个token去做访问。

构成部分:
- Header:头部,JWT的元数据,描述token本身的数据,一个Json对象。
- Payload:负载,数据内容,一个Json对象。
- Signature:签名,根据Header和token生成。(用于证明token是否被人为篡改)
JWT文档:https://github.com/golang-jwt/jwt (opens new window)
# 1)数据加密和解密
//web/user.go
func (u *UserHandler) Login(ctx *gin.Context) {
type LoginForm struct {
Email string `json:"email"`
Password string `json:"password"`
}
var req LoginForm
if err := ctx.Bind(&req); err != nil {
return
}
user, err := u.svc.Login(ctx, req.Email, req.Password)
if err == service.ErrInvalidUserOrPassword {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
if err != nil {
ctx.String(http.StatusInternalServerError, "system error : "+err.Error())
return
}
//使用JWT设置登录态
//生成一个Token
token := jwt.New(jwt.SigningMethodHS512)
tokenStr, err := token.SignedString([]byte("SPJbeQTIXdpJ7lSzidrOVoWsaEbLdZFB"))
//不为空,token不对
if err != nil {
ctx.String(http.StatusInternalServerError, "system error : "+err.Error())
return
}
ctx.Header("Authorization", "Bearer "+tokenStr)
fmt.Println(user)
ctx.String(http.StatusOK, "login success")
}
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
# 2)登录校验
//middle/login.go
func (l *LoginMiddlewareBuilder) Build() gin.HandlerFunc {
return func(ctx *gin.Context) {
//不需要登录校验的路径
for _, path := range l.paths {
if ctx.Request.URL.Path == path {
return
}
}
//使用token校验
tokenHeader := ctx.GetHeader("Authorization")
if tokenHeader == "" {
//没登录
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
segs := strings.Split(tokenHeader, " ")
if len(segs) != 2 {
//没有两段,瞎搞
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
tokenStr := segs[1]
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
return []byte("SPJbeQTIXdpJ7lSzidrOVoWsaEbLdZFB"), nil
})
if err != nil {
//没登陆
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
if token == nil || !token.Valid || claims.Uid == 0 {
//没登陆
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}
//塞入claims
ctx.Set("claims", claims)
}
}
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
# 3)访问使用JWT
//web/user.go
func (u *UserHandler) Profile(ctx *gin.Context) {
c, ok := ctx.Get("claims")
//可以断定,必然有claims
if !ok {
//监控一下
ctx.String(http.StatusUnauthorized, "unauthorized: no claims")
return
}
//类型断言
claims, ok := c.(*UserClaims)
if !ok {
ctx.String(http.StatusUnauthorized, "unauthorized: type wrong")
}
user, err := u.svc.Profile(ctx, claims.Uid)
if err != nil {
ctx.String(http.StatusInternalServerError, "system error : "+err.Error())
}
type User struct {
Nickname string `json:"nickname"`
Email string `json:"email"`
SelfDescription string `json:"selfDescription"`
Birthday string `json:"birthday"`
}
ctx.JSON(http.StatusOK, User{
Nickname: user.Nickname,
Email: user.Email,
SelfDescription: user.SelfDescription,
Birthday: user.Birthday.Format(time.DateOnly),
})
}
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
优点:
1、不依赖于第三方存储
2、适合在分布式环境下使用
3、提高性能(不需要使用到redis访问)
缺点:
1、对加密的依赖程度大,比Session更容易泄密
2、最好不要在JWT中放置敏感信息(如果真的需要放置,考虑将信息放置在Session中使用,即混用JWT和Session)
# 四、限流(最常见的保护系统方式)
使用原理:限制系统处理的请求数量。

常见算法:滑动窗口,固定窗口计数器,令牌桶,漏桶,自适应限流……
Q:我如何认定谁是谁?我如何确定限流的阈值?
A:使用IP进行限定。(更好的选择是用MAC地址或者设备标识符【CPU序列号】)。
限流的阈值正常应该是通过压测来得到的。
Q:为什么大多的限流都是使用的Redis?
A:限流需要在高并发下快速判断并修改计数器。Redis本身可以持久化状态,并且作为独立于应用的中心化存储,天然支持分布式限流;单线程模型和原子命令(如 INCR、EXPIRE,或 Lua 脚本)完美契合这个需求,能保证计数准确且速度快。
