JWT实现授权认证
一. 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协议进行传输。
参考:
关于oauth2.0
上一篇: 利用接口及抽象类设计实现