Golang JWT
http的无状态催生了cookie 和seesion
Cookie
- cookie是保存在客户端中的客户端相关的用户信息:用户名密码等可以用于服务端的二次验证,初始访问时的客户端请求不携带cookie,服务端会为其添加对应的cookie字段二次访问时cookie和客户端请求一起发送到服务端
- Cookie分为会话Cookie和持久Cookie:
- 会话Cookie不保存到硬盘上,生命周期到浏览器关闭
- 持久Cookie保存到磁盘上,生存周期由设置的MaxAge决定
- Cookie分为会话Cookie和持久Cookie:
Session
- Session是服务端保存的客户端的操作历史,使用唯一的Session-id来标识客户端和服务器之间的Session会话
- Session通过客户端Cookie保存Session-id,cookie因为所有的记录保存在客户端所以有安全隐患
- 对于做了负载均衡的session服务访问容易出现在一个服务器上的session在其它服务器上不存在因此需要同步管理
Token
-
Token是对用户身份的标识信息:一般是用户标识和时间戳签名等组成的哈希后的十六进制字符串
JWT(Json Web Token)
* 通信流程:用户向服务器发送注册信息后服务器生成JWT给客户端,客户端由此在下一次访问时将JWT带上,服务器使用特定的signature中定义的解码方式将其解码
* JWT Token包含三个部分
-header:算法和token类型的标识
-Payload: 必须使用 sub key 来指定用户 ID, 还可以包括其他信息比如 email, username 等.主要是为了承载内容而存在
规范的字段:
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
-Signature: 用来保证 JWT 的真实性. 可以使用不同算法
* JWT就是:
加密算法(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)其中的secrete是一个随机的字符串
```bash
token:=jwt.New(jwt.SigningMethodHS256)//设置加密类型
claims := token.Claims.(jwt.mMapClais)
claims["name"] = "hades"
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
token.Claims=claims
//
t, err := token.SignedString([]byte("secret"))//将secret加入
/*
也可使用如下构造
token:=jwt.NewWithClaims(jwt.SigningMethodHS256,jwt.MapClaims{
"username":user.UserName,
"password":user.PassWord,
})
reply.Token,err=token.SignedString([]byte("secret"))
*/
var jwtSecret = []byte(setting.JwtSecret)
type Claims struct {
Username string `json:"username"`
Password string `json:"password"`
jwt.StandardClaims
}
func GenerateToken(username, password string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(3 * time.Hour)//为token添加过期时间
claims := Claims{
username,
password,
jwt.StandardClaims {
ExpiresAt : expireTime.Unix(),
Issuer : "gin-blog",
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}
其中
func(token *jwt.Token) (interface{}, error){
}
属于固定格式的函数编写
func (m MapClaims) Valid() 验证基于时间的声明exp, iat, nbf,注意如果没有任何声明在令牌中,仍然会被认为是有效的。并且对于时区偏差没有计算方法
简单实例:
package main
import (
"log"
"net/http"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
)
func PostInfo(context *gin.Context) {
context.Request.ParseForm()
log.Printf("name:%v\n", context.PostForm("name"))
log.Printf("id:%v\n", context.DefaultPostForm("id", "123456"))
log.Println("Body:", context.Request.Form)
log.Println("Query name:", context.Query("name")) //依赖于URL后面的参数
log.Println("Query info:", context.DefaultQuery("info", "vvvvery "))
}
func main() {
app := gin.Default()
app.GET("/auth", Auth)
app.GET("authtest", AuthTest)
app.Run(":8080")
}
func GetToken(username, password string) string {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": username,
"password": password,
})
res, _ := token.SignedString([]byte("secret"))
return res
}
func Auth(context *gin.Context) {
username := context.Query("username")
password := context.Query("password")
context.JSON(http.StatusOK, gin.H{
"status": "ok",
"token": GetToken(username, password),
})
}
func AuthTest(context *gin.Context) {
username := context.Query("username")
password := context.Query("password")
token := context.Query("token")
/*
从请求URL中获取对应的参数
*/
if token == "" {
context.JSON(http.StatusBadRequest, gin.H{
"status": http.StatusBadRequest,
"info": "Access Permision Denied",
})
return
//直接在验证是生成一个token很不方便,应当使用像redis这样的数据库存放所有的token,比较好的是redis还可以设置过期时间
} else if token == GetToken(username, password) {
context.JSON(http.StatusOK, gin.H{
"status": http.StatusOK,
"info": "Access Success",
})
return
} else {
context.JSON(http.StatusBadRequest, gin.H{
"status": http.StatusBadRequest,
"info": "tocken error:Access Permision Denied",
})
return
}
}