青训营抖音项目实现-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的存在位置不同写了不同的认证)。工作很简单,
- 首先获取token,c.Query()指的是从url中
问号后的A="c"获取,C.PostForm()从form表单获取 - 解析token获取认证信息
- 判断是否验证成功,失败转发到登录页
- 成功,向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()
一般会经过加密