Skip to content
This repository was archived by the owner on Nov 11, 2024. It is now read-only.

Commit 7b4bdfb

Browse files
authored
Merge pull request #14 from mingzaily/master
修改example代码,新增退出功能
2 parents 01494e1 + 5969f74 commit 7b4bdfb

File tree

10 files changed

+301
-128
lines changed

10 files changed

+301
-128
lines changed

auth_error.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package jwt
2+
3+
import "errors"
4+
5+
var (
6+
// ErrMissingSecretKey indicates Secret key is required
7+
ErrMissingSecretKey = errors.New("secret key is required")
8+
9+
// ErrForbidden when HTTP status 403 is given
10+
ErrForbidden = errors.New("you don't have permission to access this resource")
11+
12+
// ErrMissingAuthenticatorFunc indicates Authenticator is required
13+
ErrMissingAuthenticatorFunc = errors.New("GfJWTMiddleware.Authenticator func is undefined")
14+
15+
// ErrMissingLoginValues indicates a user tried to authenticate without username or password
16+
ErrMissingLoginValues = errors.New("missing Username or Password")
17+
18+
// ErrFailedAuthentication indicates authentication failed, could be faulty username or password
19+
ErrFailedAuthentication = errors.New("incorrect Username or Password")
20+
21+
// ErrFailedTokenCreation indicates JWT Token failed to create, reason unknown
22+
ErrFailedTokenCreation = errors.New("failed to create JWT Token")
23+
24+
// ErrExpiredToken indicates JWT token has expired. Can't refresh.
25+
ErrExpiredToken = errors.New("token is expired")
26+
27+
// ErrInvalidToken indicates JWT token has invalid. Can't refresh.
28+
ErrInvalidToken = errors.New("token is invalid")
29+
30+
// ErrEmptyAuthHeader can be thrown if authing with a HTTP header, the Auth header needs to be set
31+
ErrEmptyAuthHeader = errors.New("auth header is empty")
32+
33+
// ErrMissingExpField missing exp field in token
34+
ErrMissingExpField = errors.New("missing exp field")
35+
36+
// ErrWrongFormatOfExp field must be float64 format
37+
ErrWrongFormatOfExp = errors.New("exp must be float64 format")
38+
39+
// ErrInvalidAuthHeader indicates auth header is invalid, could for example have the wrong Realm name
40+
ErrInvalidAuthHeader = errors.New("auth header is invalid")
41+
42+
// ErrEmptyQueryToken can be thrown if authing with URL Query, the query token variable is empty
43+
ErrEmptyQueryToken = errors.New("query token is empty")
44+
45+
// ErrEmptyCookieToken can be thrown if authing with a cookie, the token cokie is empty
46+
ErrEmptyCookieToken = errors.New("cookie token is empty")
47+
48+
// ErrEmptyParamToken can be thrown if authing with parameter in path, the parameter in path is empty
49+
ErrEmptyParamToken = errors.New("parameter token is empty")
50+
51+
// ErrInvalidSigningAlgorithm indicates signing algorithm is invalid, needs to be HS256, HS384, HS512, RS256, RS384 or RS512
52+
ErrInvalidSigningAlgorithm = errors.New("invalid signing algorithm")
53+
54+
// ErrNoPrivKeyFile indicates that the given private key is unreadable
55+
ErrNoPrivKeyFile = errors.New("private key file unreadable")
56+
57+
// ErrNoPubKeyFile indicates that the given public key is unreadable
58+
ErrNoPubKeyFile = errors.New("public key file unreadable")
59+
60+
// ErrInvalidPrivKey indicates that the given private key is invalid
61+
ErrInvalidPrivKey = errors.New("private key invalid")
62+
63+
// ErrInvalidPubKey indicates the the given public key is invalid
64+
ErrInvalidPubKey = errors.New("public key invalid")
65+
)

auth_jwt.go

+106-67
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package jwt
22

33
import (
44
"crypto/rsa"
5-
"errors"
65
"github.com/dgrijalva/jwt-go"
6+
"github.com/gogf/gf/crypto/gmd5"
77
"github.com/gogf/gf/frame/g"
88
"github.com/gogf/gf/net/ghttp"
9+
"github.com/gogf/gf/os/gcache"
910
"io/ioutil"
1011
"net/http"
1112
"strings"
@@ -68,6 +69,9 @@ type GfJWTMiddleware struct {
6869
// User can define own RefreshResponse func.
6970
RefreshResponse func(*ghttp.Request, int, string, time.Time)
7071

72+
// User can define own LogoutResponse func.
73+
LogoutResponse func(*ghttp.Request, int)
74+
7175
// Set the identity handler function
7276
IdentityHandler func(*ghttp.Request) interface{}
7377

@@ -128,65 +132,10 @@ type GfJWTMiddleware struct {
128132
}
129133

130134
var (
131-
// ErrMissingSecretKey indicates Secret key is required
132-
ErrMissingSecretKey = errors.New("secret key is required")
133-
134-
// ErrForbidden when HTTP status 403 is given
135-
ErrForbidden = errors.New("you don't have permission to access this resource")
136-
137-
// ErrMissingAuthenticatorFunc indicates Authenticator is required
138-
ErrMissingAuthenticatorFunc = errors.New("GfJWTMiddleware.Authenticator func is undefined")
139-
140-
// ErrMissingLoginValues indicates a user tried to authenticate without username or password
141-
ErrMissingLoginValues = errors.New("missing Username or Password")
142-
143-
// ErrFailedAuthentication indicates authentication failed, could be faulty username or password
144-
ErrFailedAuthentication = errors.New("incorrect Username or Password")
145-
146-
// ErrFailedTokenCreation indicates JWT Token failed to create, reason unknown
147-
ErrFailedTokenCreation = errors.New("failed to create JWT Token")
148-
149-
// ErrExpiredToken indicates JWT token has expired. Can't refresh.
150-
ErrExpiredToken = errors.New("token is expired")
151-
152-
// ErrEmptyAuthHeader can be thrown if authing with a HTTP header, the Auth header needs to be set
153-
ErrEmptyAuthHeader = errors.New("auth header is empty")
154-
155-
// ErrMissingExpField missing exp field in token
156-
ErrMissingExpField = errors.New("missing exp field")
157-
158-
// ErrWrongFormatOfExp field must be float64 format
159-
ErrWrongFormatOfExp = errors.New("exp must be float64 format")
160-
161-
// ErrInvalidAuthHeader indicates auth header is invalid, could for example have the wrong Realm name
162-
ErrInvalidAuthHeader = errors.New("auth header is invalid")
163-
164-
// ErrEmptyQueryToken can be thrown if authing with URL Query, the query token variable is empty
165-
ErrEmptyQueryToken = errors.New("query token is empty")
166-
167-
// ErrEmptyCookieToken can be thrown if authing with a cookie, the token cokie is empty
168-
ErrEmptyCookieToken = errors.New("cookie token is empty")
169-
170-
// ErrEmptyParamToken can be thrown if authing with parameter in path, the parameter in path is empty
171-
ErrEmptyParamToken = errors.New("parameter token is empty")
172-
173-
// ErrInvalidSigningAlgorithm indicates signing algorithm is invalid, needs to be HS256, HS384, HS512, RS256, RS384 or RS512
174-
ErrInvalidSigningAlgorithm = errors.New("invalid signing algorithm")
175-
176-
// ErrNoPrivKeyFile indicates that the given private key is unreadable
177-
ErrNoPrivKeyFile = errors.New("private key file unreadable")
178-
179-
// ErrNoPubKeyFile indicates that the given public key is unreadable
180-
ErrNoPubKeyFile = errors.New("public key file unreadable")
181-
182-
// ErrInvalidPrivKey indicates that the given private key is invalid
183-
ErrInvalidPrivKey = errors.New("private key invalid")
184-
185-
// ErrInvalidPubKey indicates the the given public key is invalid
186-
ErrInvalidPubKey = errors.New("public key invalid")
187-
188135
// IdentityKey default identity key
189136
IdentityKey = "identity"
137+
// The blacklist stores tokens that have not expired but have been deactivated.
138+
blacklist = gcache.New()
190139
)
191140

192141
// New for check error with GfJWTMiddleware
@@ -303,6 +252,15 @@ func (mw *GfJWTMiddleware) MiddlewareInit() error {
303252
}
304253
}
305254

255+
if mw.LogoutResponse == nil {
256+
mw.LogoutResponse = func(r *ghttp.Request, code int) {
257+
r.Response.WriteJson(g.Map{
258+
"code": http.StatusOK,
259+
"message": "success",
260+
})
261+
}
262+
}
263+
306264
if mw.IdentityKey == "" {
307265
mw.IdentityKey = IdentityKey
308266
}
@@ -346,7 +304,7 @@ func (mw *GfJWTMiddleware) MiddlewareFunc() ghttp.HandlerFunc {
346304
}
347305

348306
func (mw *GfJWTMiddleware) middlewareImpl(r *ghttp.Request) {
349-
claims, err := mw.GetClaimsFromJWT(r)
307+
claims, token, err := mw.GetClaimsFromJWT(r)
350308
if err != nil {
351309
mw.unauthorized(r, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, r))
352310
return
@@ -367,6 +325,17 @@ func (mw *GfJWTMiddleware) middlewareImpl(r *ghttp.Request) {
367325
return
368326
}
369327

328+
in, err := mw.inBlacklist(token)
329+
if err != nil {
330+
mw.unauthorized(r, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, r))
331+
return
332+
}
333+
334+
if in {
335+
mw.unauthorized(r, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrInvalidToken, r))
336+
return
337+
}
338+
370339
r.SetParam("JWT_PAYLOAD", claims)
371340
identity := mw.IdentityHandler(r)
372341

@@ -383,11 +352,11 @@ func (mw *GfJWTMiddleware) middlewareImpl(r *ghttp.Request) {
383352
}
384353

385354
// GetClaimsFromJWT get claims from JWT token
386-
func (mw *GfJWTMiddleware) GetClaimsFromJWT(r *ghttp.Request) (MapClaims, error) {
355+
func (mw *GfJWTMiddleware) GetClaimsFromJWT(r *ghttp.Request) (MapClaims, string, error) {
387356
token, err := mw.ParseToken(r)
388357

389358
if err != nil {
390-
return nil, err
359+
return nil, "", err
391360
}
392361

393362
if mw.SendAuthorization {
@@ -402,7 +371,7 @@ func (mw *GfJWTMiddleware) GetClaimsFromJWT(r *ghttp.Request) (MapClaims, error)
402371
claims[key] = value
403372
}
404373

405-
return claims, nil
374+
return claims, token.Raw, nil
406375
}
407376

408377
// LoginHandler can be used by clients to get a jwt token.
@@ -461,6 +430,25 @@ func (mw *GfJWTMiddleware) signedString(token *jwt.Token) (string, error) {
461430
return tokenString, err
462431
}
463432

433+
// LogoutHandler can be used to logout a token. The token still needs to be valid on logout.
434+
// Logout the token puts the unexpired token on a blacklist.
435+
func (mw *GfJWTMiddleware) LogoutHandler(r *ghttp.Request) {
436+
claims, token, err := mw.CheckIfTokenExpire(r)
437+
if err != nil {
438+
mw.unauthorized(r, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, r))
439+
return
440+
}
441+
442+
err = mw.setBlacklist(token, claims)
443+
444+
if err != nil {
445+
mw.unauthorized(r, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, r))
446+
return
447+
}
448+
449+
mw.LogoutResponse(r, http.StatusOK)
450+
}
451+
464452
// RefreshHandler can be used to refresh a token. The token still needs to be valid on refresh.
465453
// Shall be put under an endpoint that is using the GfJWTMiddleware.
466454
// Reply will be of the form {"token": "TOKEN"}.
@@ -476,7 +464,7 @@ func (mw *GfJWTMiddleware) RefreshHandler(r *ghttp.Request) {
476464

477465
// RefreshToken refresh token and check if token is expired
478466
func (mw *GfJWTMiddleware) RefreshToken(r *ghttp.Request) (string, time.Time, error) {
479-
claims, err := mw.CheckIfTokenExpire(r)
467+
claims, token, err := mw.CheckIfTokenExpire(r)
480468
if err != nil {
481469
return "", time.Now(), err
482470
}
@@ -504,11 +492,17 @@ func (mw *GfJWTMiddleware) RefreshToken(r *ghttp.Request) (string, time.Time, er
504492
r.Cookie.SetCookie(mw.CookieName, tokenString, mw.CookieDomain, "/", time.Duration(maxage)*time.Second)
505493
}
506494

495+
// set old token in blacklist
496+
err = mw.setBlacklist(token, claims)
497+
if err != nil {
498+
return "", time.Now(), err
499+
}
500+
507501
return tokenString, expire, nil
508502
}
509503

510504
// CheckIfTokenExpire check if token expire
511-
func (mw *GfJWTMiddleware) CheckIfTokenExpire(r *ghttp.Request) (jwt.MapClaims, error) {
505+
func (mw *GfJWTMiddleware) CheckIfTokenExpire(r *ghttp.Request) (jwt.MapClaims, string, error) {
512506
token, err := mw.ParseToken(r)
513507

514508
if err != nil {
@@ -519,19 +513,29 @@ func (mw *GfJWTMiddleware) CheckIfTokenExpire(r *ghttp.Request) (jwt.MapClaims,
519513
// (see https://github.com/appleboy/gin-jwt/issues/176)
520514
validationErr, ok := err.(*jwt.ValidationError)
521515
if !ok || validationErr.Errors != jwt.ValidationErrorExpired {
522-
return nil, err
516+
return nil, "", err
523517
}
524518
}
525519

520+
in, err := mw.inBlacklist(token.Raw)
521+
522+
if err != nil {
523+
return nil, "", err
524+
}
525+
526+
if in {
527+
return nil, "", ErrInvalidToken
528+
}
529+
526530
claims := token.Claims.(jwt.MapClaims)
527531

528532
origIat := int64(claims["orig_iat"].(float64))
529533

530534
if origIat < mw.TimeFunc().Add(-mw.MaxRefresh).Unix() {
531-
return nil, ErrExpiredToken
535+
return nil, "", ErrExpiredToken
532536
}
533537

534-
return claims, nil
538+
return claims, token.Raw, nil
535539
}
536540

537541
// TokenGenerator method that clients can use to get a jwt token.
@@ -653,6 +657,41 @@ func (mw *GfJWTMiddleware) unauthorized(r *ghttp.Request, code int, message stri
653657

654658
}
655659

660+
func (mw *GfJWTMiddleware) setBlacklist(token string, claims jwt.MapClaims) error {
661+
// The goal of MD5 is to reduce the key length.
662+
token, err := gmd5.EncryptString(token)
663+
664+
if err != nil {
665+
return err
666+
}
667+
668+
exp := int64(claims["exp"].(float64))
669+
670+
// Global gcache
671+
err = blacklist.Set(token, true, time.Unix(exp, 0).Sub(mw.TimeFunc()))
672+
if err != nil {
673+
return err
674+
}
675+
676+
return nil
677+
}
678+
679+
func (mw *GfJWTMiddleware) inBlacklist(token string) (bool, error) {
680+
// The goal of MD5 is to reduce the key length.
681+
tokenRaw, err := gmd5.EncryptString(token)
682+
683+
if err != nil {
684+
return false, nil
685+
}
686+
687+
// Global gcache
688+
if in, err := blacklist.Contains(tokenRaw); err != nil {
689+
return false, nil
690+
} else {
691+
return in, nil
692+
}
693+
}
694+
656695
// ExtractClaims help to extract the JWT claims
657696
func ExtractClaims(r *ghttp.Request) MapClaims {
658697
claims := r.GetParam("JWT_PAYLOAD")

0 commit comments

Comments
 (0)