一. JWT是什么
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令(token),用来代替密码,供第三方应用使用。。
传统的授权认证方式,需要持久化session数据,写入数据库或文件持久层等,且授权校验依赖于数据持久层。 这样的方式,对于结构维护成本大
,实现单点登录较为复杂
,且没有分布式架构,无法支持横向扩展
,风险较大
(如果持久层失败,整个认证体系都会挂掉)。
JWT则无须持久化会话数据,是以加密签名的方式实现了用户身份认证授权,很好的解决了跨域身份验证
,分布式session共享
、单点登录
和横向扩展
等问题。
二. JWT标准规范
JWT由三部分组成,即头部
、负载
与签名
。Token格式为 token=头部+'.'+载荷+'.'+签名
。
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Nzg0MDQ5OTUsImlhdCI6MTU3ODQwMTM5NX0.waG8rvOZLM2pKDeKg7frMKlV8lAty1Og5LDjrVMJRsI"}
1. Header: 用于说明签名的加密算法等,下面类型的json经过base64编码后得到JWT头部。
{ "typ": "JWT", "alg": "HS256" }
2. Payload: 标准定义了7个字段,载荷json经过base64编码后得到JWT的载荷。
{ iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号 }
示例:
{ "sub": "1", "iss": "http://localhost:8000/user/sign_up", "iat": 1451888119, "exp": 1454516119, "nbf": 1451888119, "jti": "37c107e4609ddbcc9c096ea5ee76c667" }
3.Signature: 将头部和载荷用'.'号连接,再加上一串密钥,经过头部声明的加密算法加密后得到签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
三. 核心代码简析
1. 数据结构
// A JWT Token. Different fields will be used depending on whether you're // creating or parsing/verifying a token. type Token struct { Raw string // The raw token. Populated when you Parse a token Method SigningMethod // The signing method used or to be used Header map[string]interface{} // (头部)The first segment of the token Claims Claims // (负载)The second segment of the token Signature string // (签名)The third segment of the token. Populated when you Parse a token Valid bool // Is the token valid? Populated when you Parse/Verify a token }
// Structured version of Claims Section, as referenced at // https://tools.ietf.org/html/rfc7519#section-4.1 // See examples for how to use this with your own claim types type StandardClaims struct { Id string `json:"jti,omitempty"` //编号 Subject string `json:"sub,omitempty"` //主题 Issuer string `json:"iss,omitempty"` //签发人 Audience string `json:"aud,omitempty"` //受众 ExpiresAt int64 `json:"exp,omitempty"` //过期时间 IssuedAt int64 `json:"iat,omitempty"` //签发时间 NotBefore int64 `json:"nbf,omitempty"` //生效时间 }
2. 生成Token
var( key []byte = []byte("This is secret!") ) // 产生json web token func GenToken() string { claims := &jwt.StandardClaims{ NotBefore: int64(time.Now().Unix()), ExpiresAt: int64(time.Now().Unix() + 1000), Issuer: "Bitch", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) ss, err := token.SignedString(key) if err != nil { logs.Error(err) return "" } return ss }
3. 校验Token
// 校验token是否有效 func CheckToken(token string) bool { _, err := jwt.Parse(token, func(*jwt.Token) (interface{}, error) { return key, nil }) if err != nil { fmt.Println("parase with claims failed.", err) return false } return true }
四. 登录授权示例
handler/auth.go
package handler import ( "fmt" "github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go/request" "net/http" ) const ( SecretKey = "ODcyNDYsIMzY0N" ) func ValidateTokenMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor, func(token *jwt.Token) (interface{}, error) { return []byte(SecretKey), nil }) if err == nil { if token.Valid { next(w, r) } else { w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, "Token is not valid") } } else { w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, "Unauthorized access to this resource") } }
handler/account.go
package handler import ( "encoding/json" "fmt" "github.com/dgrijalva/jwt-go" "log" "net/http" "strings" "time" ) func fatal(err error) { if err != nil { log.Fatal(err) } } type UserCredentials struct { Username string `json:"username"` Password string `json:"password"` } type User struct { ID int `json:"id"` Name string `json:"name"` Username string `json:"username"` Password string `json:"password"` } type Response struct { Data string `json:"data"` } type Token struct { Token string `json:"token"` } func LoginHandler(w http.ResponseWriter, r *http.Request) { var user UserCredentials err := json.NewDecoder(r.Body).Decode(&user) if err != nil { w.WriteHeader(http.StatusForbidden) fmt.Fprint(w, "Error in request") return } if strings.ToLower(user.Username) != "admin" { if user.Password != "123456" { w.WriteHeader(http.StatusForbidden) fmt.Fprint(w, "Invalid credentials") return } } // 创建Token token := jwt.New(jwt.SigningMethodHS256) claims := make(jwt.MapClaims) claims["exp"] = time.Now().Add(time.Hour * time.Duration(1)).Unix() claims["iat"] = time.Now().Unix() token.Claims = claims //if err != nil { // w.WriteHeader(http.StatusInternalServerError) // fmt.Fprintln(w, "Error extracting the key") // fatal(err) //} tokenString, err := token.SignedString([]byte(SecretKey)) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintln(w, "Error while signing the token") fatal(err) } response := Token{tokenString} JsonResponse(response, w) } func FundHandler(w http.ResponseWriter, r *http.Request) { response := Response{"账户余额:1000W"} JsonResponse(response, w) } func JsonResponse(response interface{}, w http.ResponseWriter) { obj, err := json.Marshal(response) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") w.Write(obj) }
main.go
import ( "github.com/codegangsta/negroni" "log" "net/http" "proxy/handler" ) func Server() { http.HandleFunc("/login", handler.LoginHandler) http.Handle("/data", negroni.New( negroni.HandlerFunc(handler.ValidateTokenMiddleware), //中间件 negroni.Wrap(http.HandlerFunc(handler.FundHandler)), )) log.Println("服务已启动...") http.ListenAndServe(":8080", nil) } func main() { Server() }
五. JWT 使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie
里面,也可以储存在 localStorage
。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域
。所以更好的做法是放在HTTP请求的头信息Authorization
字段里面(或放在POST 请求的数据体里面)。
Authorization: Bearer <token>
六. JWT注意事项
- JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。
- JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
- JWT的最大缺点是服务器不保存会话状态,一旦JWT签发,在有效期内将会一直有效。
- JWT的有效期不宜设置太长,认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。
- 为了减少JWT数据盗用和窃取的风险,JWT建议使用加密的HTTPS协议进行传输。
参考:
http://www.ruanyifeng.com/blog/2019/04/oauth_design.html关于OAuth2.0
https://blog.csdn.net/wangshubo1989/article/details/74529333
https://blog.csdn.net/idwtwt/article/details/80865209
https://baijiahao.baidu.com/s?id=1608021814182894637&wfr=spider&for=pc
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性