jwt(json web token)
jwt
jwt的原理和session有点相像,其目的是为了解决rest api中无状态性
因为rest接口,需要权限校验。但是又不能每个请求都把用户名密码传入,因此产生了这个token的方法
流程:
https://blog.wangjunfeng.com/post/golang-jwt/#3-%e7%ad%be%e5%90%8d-signature
用户访问auth接口,获取token
服务器校验用户传入的用户名密码等信息,确认无误后,产生一个token。
这个token其实是类似于map的数据结构(jwt数据结构)中的key。准确的应该是:token中其实就保存了用户的信息,只是被加密过了。怪不得服务器重启了token还能使用,就是这个原因,因为数据就是保存在token这条长长的字符串中的。
用户访问需要权限验证的接口,并传入token。
服务器验证token:根据自己的token密钥判断token是否正确(是否被别人篡改),正确后才从token中解析出token中的信息。可能会把解析出的信息保存在context中
使用步骤
下载依赖包
go get -u github.com/dgrijalva/jwt-go
编写jwt工具包,用户创建和检查token
分为几个部分:
- 指定加密密钥
- 指定被保存在token中的实体对象,claims 结构体。需要内嵌jwt.standardclaims。这个结构体是用来保存信息的。
- 根据数据产生token:根据传入的信息,组装成一个claims结构体对象,再从对象中获取token
- 根据token解析数据:解析出token所对应的interface{},再使用断言解析出claims对象,取数据
/ 指定加密密钥 var jwtsecret=[]byte(setting.jwtsecret) //claim是一些实体(通常指的用户)的状态和额外的元数据 type claims struct{ username string `json:"username"` password string `json:"password"` jwt.standardclaims } // 根据用户的用户名和密码产生token func generatetoken(username ,password string)(string,error){ //设置token有效时间 nowtime:=time.now() expiretime:=nowtime.add(3*time.hour) claims:=claims{ username: username, password: password, standardclaims: jwt.standardclaims{ // 过期时间 expiresat:expiretime.unix(), // 指定token发行人 issuer:"gin-blog", }, } tokenclaims:=jwt.newwithclaims(jwt.signingmethodhs256,claims) //该方法内部生成签名字符串,再用于获取完整、已签名的token token,err:=tokenclaims.signedstring(jwtsecret) return token,err } // 根据传入的token值获取到claims对象信息,(进而获取其中的用户名和密码) func parsetoken(token string)(*claims,error){ //用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*token tokenclaims, err := jwt.parsewithclaims(token, &claims{}, func(token *jwt.token) (interface{}, error) { return jwtsecret, nil }) if tokenclaims!=nil{ // 从tokenclaims中获取到claims对象,并使用断言,将该对象转换为我们自己定义的claims // 要传入指针,项目中结构体都是用指针传递,节省空间。 if claims,ok:=tokenclaims.claims.(*claims);ok&&tokenclaims.valid{ return claims,nil } } return nil,err }
编写路由,返回token
(需要做用户参数校验与错误处理)
- 用户参数校验
type auth struct{ username string `valid:"required;maxsize(50)"` password string `valid:"required;maxsize(50)"` } func getauth(c *gin.context){ username:=c.query("username") password:=c.query("password") valid:=validation.validation{} a:=auth{ username: username, password: password, } // 与之前的对每一个数据分开验证不同,此处在auth对象中通过定义标签valid // 一次性校验对象中的所有字段信息 ok,_:=valid.valid(&a) //创建返回信息 data:=make(map[string]interface{}) code:=e.invalid_params /* 根据用户名密码获取token 判断流程: 1. 先判断用户名密码是否存在 */ if ok{ isexist:=models.checkauth(username,password) if isexist{ token,err:=util.generatetoken(username,password) if err!=nil{ code=e.error_auth_token }else{ data["token"]=token code=e.success } }else{ code=e.error_auth } }else{ //如果数据验证失败,则打印结果 for _,err:=range valid.errors{ log.println(err.key,err.message) } } c.json(http.statusok,util.returndata(code,e.getmsg(code),data)) }
编写中间件,校验token字符串
func jwy()gin.handlerfunc{ return func(c *gin.context){ var code int var data interface{} code=e.success token:=c.query("token") if token==""{ code=e.error_auth_no_tokrn }else{ claims,err:=util.parsetoken(token) if err!=nil{ code=e.error_auth_check_token_fail }else if time.now().unix()>claims.expiresat{ code=e.error_auth_check_token_timeout } } //如果token验证不通过,直接终止程序,c.abort() if code!=e.success{ // 返回错误信息 c.json(http.statusunauthorized,util.returndata(code,e.getmsg(code),data)) //终止程序 c.abort() return } c.next() } }
?