这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
前言
在大项目编写过程中,涉及到了安全验证,我学习并采用了jwt这一解决方案
一.概念
jwt,全名(json web token),是一种跨域的认证的解决方案,属于一个开放的标准。使用其规定了一种token的实现方式。
二.为什么使用
传统的的web项目,使用的都是session来认证用户的信息,具体的流程如下:
1.用户通过浏览器将账号跟密码传输给后台服务。
2.服务端对用户跟密码校验后会生成一份保存当前用户信息的session和一个对应的session_id。
3.如果返回响应的时候会将生成的session_id一并返回,浏览器会将该值写入cookie中。
4.如果用户再次通过该浏览器访问服务时,都会携带含有session_id的cookie信息至服务端。
5.服务端接收到请求后,利用cookie中session_id查找对应的账号相关的session信息,从而再次验证当前请求。
6.对于非浏览器的客户端、手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie因为session认证本质基于cookie,所以如果cookie被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie,这种方式也会失效,前后端分离系统中更加不适用,后端部署复杂,前端发送的请求往往经过多个中间件到达后端,cookie中关于session的信息会转发多次。cookie无法跨域,不适用于单点登录。
存在的缺陷:
1.极度依赖浏览器保存cookie机制。并且再服务端,需要为每一个账号都创建一个session来保存相关信息。(存储消耗较大,如果用户量大,分布式存储也会持续的增大并且维护力度增加)。
2.用户登录方式多样化(不可能总是同一个浏览器,限制了自由度),例如用户使用第三方或者app来访问。如果再次使用cookie模式,就会很是有1中的问题,并且也不灵活。
三.jwt的使用
2.结构:
jwt-token由三部分组成,header + payload + signature组成。公式如下:
JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
header部分:
是一个json对象:
{
"typ": "JWT",
"alg": 加密方法,例如(HS256)
}
payload:
有效载荷部分,jwt主题部分,
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除此之外,可以可以根据需要自定义相关的信息
{
"user_id": 0
"username":"",
}
完整结构例如
cla := MyClaims{
username,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
Issuer: "lx-jwt", // 签发人
},
}
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}
Signature:
签名是对上面两部分的数据进行签名,过程是使用base64编码后的数据,通过制定的算哈生成哈希(不可逆),这样用于确保数据不会被篡改。另外,还需要一个密钥secret,该secret仅仅保存在服务端,并且不可公开,secret不能对外泄漏,保存于服务端。
4.使用go的go-jwt实现的一个模拟获取token以及请求认证过程
package jwt_use
import (
"errors"
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
)
const TokenExpireDuration = time.Hour * 2
// const TokenExpireDuration = time.Second * 60
var Secret = []byte("人生路漫漫")
type MyClaims struct {
UserName string
jwt.StandardClaims
}
// get token
func GetToken(username string) (string, error) {
cla := MyClaims{
username,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
Issuer: "lx-jwt", // 签发人
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, cla)
fmt.Println("Token = ",token)
return token.SignedString(Secret) // 进行签名生成对应的token
}
// parse token
func ParseToken(tokenString string) (*MyClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return Secret, nil
})
if err != nil {
return nil, err
}
if claims,ok := token.Claims.(*MyClaims);ok && token.Valid {
return claims,nil
}
return nil,errors.New("invalid token")
}
总结
通过jwt生成token,并且解析token,结合gin框架的中间件,可以在项目中实现对登录权限的验证,甚至实现免登录功能。