@@ -2,10 +2,11 @@ package jwt
22
33import (
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
130134var (
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
348306func (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
478466func (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
657696func ExtractClaims (r * ghttp.Request ) MapClaims {
658697 claims := r .GetParam ("JWT_PAYLOAD" )
0 commit comments