@@ -2,10 +2,11 @@ package jwt
2
2
3
3
import (
4
4
"crypto/rsa"
5
- "errors"
6
5
"github.com/dgrijalva/jwt-go"
6
+ "github.com/gogf/gf/crypto/gmd5"
7
7
"github.com/gogf/gf/frame/g"
8
8
"github.com/gogf/gf/net/ghttp"
9
+ "github.com/gogf/gf/os/gcache"
9
10
"io/ioutil"
10
11
"net/http"
11
12
"strings"
@@ -68,6 +69,9 @@ type GfJWTMiddleware struct {
68
69
// User can define own RefreshResponse func.
69
70
RefreshResponse func (* ghttp.Request , int , string , time.Time )
70
71
72
+ // User can define own LogoutResponse func.
73
+ LogoutResponse func (* ghttp.Request , int )
74
+
71
75
// Set the identity handler function
72
76
IdentityHandler func (* ghttp.Request ) interface {}
73
77
@@ -128,65 +132,10 @@ type GfJWTMiddleware struct {
128
132
}
129
133
130
134
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
-
188
135
// IdentityKey default identity key
189
136
IdentityKey = "identity"
137
+ // The blacklist stores tokens that have not expired but have been deactivated.
138
+ blacklist = gcache .New ()
190
139
)
191
140
192
141
// New for check error with GfJWTMiddleware
@@ -303,6 +252,15 @@ func (mw *GfJWTMiddleware) MiddlewareInit() error {
303
252
}
304
253
}
305
254
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
+
306
264
if mw .IdentityKey == "" {
307
265
mw .IdentityKey = IdentityKey
308
266
}
@@ -346,7 +304,7 @@ func (mw *GfJWTMiddleware) MiddlewareFunc() ghttp.HandlerFunc {
346
304
}
347
305
348
306
func (mw * GfJWTMiddleware ) middlewareImpl (r * ghttp.Request ) {
349
- claims , err := mw .GetClaimsFromJWT (r )
307
+ claims , token , err := mw .GetClaimsFromJWT (r )
350
308
if err != nil {
351
309
mw .unauthorized (r , http .StatusUnauthorized , mw .HTTPStatusMessageFunc (err , r ))
352
310
return
@@ -367,6 +325,17 @@ func (mw *GfJWTMiddleware) middlewareImpl(r *ghttp.Request) {
367
325
return
368
326
}
369
327
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
+
370
339
r .SetParam ("JWT_PAYLOAD" , claims )
371
340
identity := mw .IdentityHandler (r )
372
341
@@ -383,11 +352,11 @@ func (mw *GfJWTMiddleware) middlewareImpl(r *ghttp.Request) {
383
352
}
384
353
385
354
// 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 ) {
387
356
token , err := mw .ParseToken (r )
388
357
389
358
if err != nil {
390
- return nil , err
359
+ return nil , "" , err
391
360
}
392
361
393
362
if mw .SendAuthorization {
@@ -402,7 +371,7 @@ func (mw *GfJWTMiddleware) GetClaimsFromJWT(r *ghttp.Request) (MapClaims, error)
402
371
claims [key ] = value
403
372
}
404
373
405
- return claims , nil
374
+ return claims , token . Raw , nil
406
375
}
407
376
408
377
// LoginHandler can be used by clients to get a jwt token.
@@ -461,6 +430,25 @@ func (mw *GfJWTMiddleware) signedString(token *jwt.Token) (string, error) {
461
430
return tokenString , err
462
431
}
463
432
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
+
464
452
// RefreshHandler can be used to refresh a token. The token still needs to be valid on refresh.
465
453
// Shall be put under an endpoint that is using the GfJWTMiddleware.
466
454
// Reply will be of the form {"token": "TOKEN"}.
@@ -476,7 +464,7 @@ func (mw *GfJWTMiddleware) RefreshHandler(r *ghttp.Request) {
476
464
477
465
// RefreshToken refresh token and check if token is expired
478
466
func (mw * GfJWTMiddleware ) RefreshToken (r * ghttp.Request ) (string , time.Time , error ) {
479
- claims , err := mw .CheckIfTokenExpire (r )
467
+ claims , token , err := mw .CheckIfTokenExpire (r )
480
468
if err != nil {
481
469
return "" , time .Now (), err
482
470
}
@@ -504,11 +492,17 @@ func (mw *GfJWTMiddleware) RefreshToken(r *ghttp.Request) (string, time.Time, er
504
492
r .Cookie .SetCookie (mw .CookieName , tokenString , mw .CookieDomain , "/" , time .Duration (maxage )* time .Second )
505
493
}
506
494
495
+ // set old token in blacklist
496
+ err = mw .setBlacklist (token , claims )
497
+ if err != nil {
498
+ return "" , time .Now (), err
499
+ }
500
+
507
501
return tokenString , expire , nil
508
502
}
509
503
510
504
// 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 ) {
512
506
token , err := mw .ParseToken (r )
513
507
514
508
if err != nil {
@@ -519,19 +513,29 @@ func (mw *GfJWTMiddleware) CheckIfTokenExpire(r *ghttp.Request) (jwt.MapClaims,
519
513
// (see https://github.com/appleboy/gin-jwt/issues/176)
520
514
validationErr , ok := err .(* jwt.ValidationError )
521
515
if ! ok || validationErr .Errors != jwt .ValidationErrorExpired {
522
- return nil , err
516
+ return nil , "" , err
523
517
}
524
518
}
525
519
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
+
526
530
claims := token .Claims .(jwt.MapClaims )
527
531
528
532
origIat := int64 (claims ["orig_iat" ].(float64 ))
529
533
530
534
if origIat < mw .TimeFunc ().Add (- mw .MaxRefresh ).Unix () {
531
- return nil , ErrExpiredToken
535
+ return nil , "" , ErrExpiredToken
532
536
}
533
537
534
- return claims , nil
538
+ return claims , token . Raw , nil
535
539
}
536
540
537
541
// 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
653
657
654
658
}
655
659
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
+
656
695
// ExtractClaims help to extract the JWT claims
657
696
func ExtractClaims (r * ghttp.Request ) MapClaims {
658
697
claims := r .GetParam ("JWT_PAYLOAD" )
0 commit comments