青训营抖音项目实现-2 | 青训营笔记

164 阅读4分钟

青训营抖音项目实现-2 | 青训营笔记

上次主要讲了表的设计和项目目录结构,这次主要介绍我的抖音项目中关于登录方面的内容

1、cookie,session,token

关于这三者的东西很多,我只说一说我的理解。

cookie

cookie是客户端临时存储数据的地方,与其他暂存数据的地方(localstorage,sessionstorage)的区别在于:这里面的数据会随着请求发给服务器(在请求头的字段:Cookie),因此服务器可以获得里面的数据进行处理。cookie有个缺点,打开浏览器F12,就可以发现cookie以域名分隔开了,因此存在跨域问题。

session

session是服务器记录这一次会话的一些信息,一般用来确认用户是否登录,鉴权等。

token

token也是一些信息,也叫做令牌,是用户身份的验证方式。 最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名)。

我大胆的认为:token就是加密后的session存放在客户端(也可以存放在cookie,放在cookie基本和cookie-session的用户验证方式完全一样)。一般存放在localstorage中,这样就有个缺点:浏览器发请求不会自动携带localstorage的信息,因此必须使用jquery或者其它js组件主动发请求从localstorage中获取token放到请求中,可以放在cookie,query,body等等。这就限制了token的使用范围,一般的非前后端分离的项目中总会有一些请求不会经过jquery(我认为的)。优点:不会有跨域问题

2、gin使用session和jwt

jwt

gin框架有中间件,中间件也是一个handler。前面讲过gin框架处理请求是责任链模式,因此我们创建一个认证中间件:

// GetAuthorization
// token存在query
// 解析token,由于get方法和post方法token的存在位置不同因此需要分别解析
// 验证失败全部redirect到登录页面
func GetAuthorization(c *gin.Context) {
   token := c.Query("token")
   auth, err := controllers.ParseToken(token)

   // 验证失败,返回登录页面登录
   if err != nil {
      utils.Redirect(c, "/douyin/user/login")
      return
   }
   c.Set("auth", *auth)
   c.Next() // 进入下一个handler处理请求

}

// PostAuthorization
// token存在form表单中
// 解析token,由于get方法和post方法token的存在位置不同因此需要分别解析
// 验证失败全部redirect到登录页面
func PostAuthorization(c *gin.Context) {
   token := c.PostForm("token")
   auth, err := controllers.ParseToken(token)

   // 验证失败,返回登录页面登录
   if err != nil {
      utils.Redirect(c, "/douyin/user/login")
      return
   }
   c.Set("auth", *auth)
   c.Next()
}

在middleware目录下创建一个auth.go。两个认证函数(基于token的存在位置不同写了不同的认证)。工作很简单,

  1. 首先获取token,c.Query()指的是从url中问号后的A="c"获取,C.PostForm()从form表单获取
  2. 解析token获取认证信息
  3. 判断是否验证成功,失败转发到登录页
  4. 成功,向gin.Context写入auth信息供后面handler使用

解析token,我主要借助了jwt-go-v4

func MakeToken(auth *Auth) (tokenString string, err error) {
   claim := Auth{
      UserName:      auth.UserName,
      UserID:        auth.UserID,
      FollowCount:   auth.FollowCount,
      FollowerCount: auth.FollowerCount,
      RegisteredClaims: jwt.RegisteredClaims{
         ExpiresAt: jwt.NewNumericDate(time.Now().Add(3 * time.Hour * time.Duration(1))), // 过期时间3小时
         IssuedAt:  jwt.NewNumericDate(time.Now()),                                       // 签发时间
         NotBefore: jwt.NewNumericDate(time.Now()),                                       // 生效时间
      },
   }
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) // 使用HS256算法
   tokenString, err = token.SignedString(MySecret)
   return tokenString, err
}

func Secret() jwt.Keyfunc {
   return func(token *jwt.Token) (interface{}, error) {
      return []byte("手写的从前"), nil // 这是我的secret
   }
}

func ParseToken(originToken string) (*Auth, error) {
   token, err := jwt.ParseWithClaims(originToken, &Auth{}, Secret())
   if err != nil {
      if ve, ok := err.(*jwt.ValidationError); ok {
         if ve.Errors&jwt.ValidationErrorMalformed != 0 {
            return nil, errors.New("that's not even a token")
         } else if ve.Errors&jwt.ValidationErrorExpired != 0 {
            return nil, errors.New("token is expired")
         } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
            return nil, errors.New("token not active yet")
         } else {
            return nil, errors.New("couldn't handle this token")
         }
      }
   }
   if claims, ok := token.Claims.(*Auth); ok && token.Valid {
      return claims, nil
   }
   return nil, errors.New("couldn't handle this token")
}

Auth结构体记录token的认证信息,主要是用户信息

type Auth struct {
   UserID        uint
   UserName      string
   FollowCount   int
   FollowerCount int
   jwt.RegisteredClaims //使用jwt需要
}
cookie-session

如果使用传统cookie-session认证方式,首先我们需要选择一个session的存储位置,在gin-contrib/session中有redis和cookie两种

redis
gob.Register(controllers.Auth{})
// 1、使用session方式
newStore, err := redis.NewStore(100, "tcp", "", "", []byte("jhg"))
if err != nil {
   return nil
}
//store := cookie.NewStore([]byte("secret"))
App.Use(sessions.Sessions(COOKIE_SESSION_KEY, newStore)) 

存在redis中我们需要指定一个key,也就是sessionID,然后我们需要把key返回到前端(一般放在cookie中)。而session就放在redis保存。这种方式避免了将session直接放到cookie。但是也有很多问题

cookie.NewStore() 

一般会经过加密