重构了 jwt , 支持黑名单机制
diff --git a/config/application-devel-pg-local.properties b/config/application-devel-pg-local.properties
index 175bb6b..ad1ccb2 100644
--- a/config/application-devel-pg-local.properties
+++ b/config/application-devel-pg-local.properties
@@ -7,6 +7,7 @@
 spring.datasource.url=jdbc:postgresql://localhost:5432/payapi
 spring.datasource.username=payapi
 spring.datasource.password=123456
+database.dbtype=postgresql
 # Redis settings
 redis.server=localhost
 redis.port=6379
diff --git a/config/application-devel-pg.properties b/config/application-devel-pg.properties
index 92b7ecf..d21bd66 100644
--- a/config/application-devel-pg.properties
+++ b/config/application-devel-pg.properties
@@ -3,6 +3,7 @@
 spring.jpa.hibernate.ddl-auto=update
 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
 spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
+database.dbtype=postgresql
 # Postgresql settings
 spring.datasource.url=jdbc:postgresql://172.28.201.70:15432/payapi
 spring.datasource.username=payapi
diff --git a/src/main/java/com/supwisdom/dlpay/framework/core/DatabaseConfig.java b/src/main/java/com/supwisdom/dlpay/framework/core/DatabaseConfig.java
new file mode 100644
index 0000000..16f454a
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/framework/core/DatabaseConfig.java
@@ -0,0 +1,18 @@
+package com.supwisdom.dlpay.framework.core;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class DatabaseConfig {
+  @Value("${database.dbtype:oracle}")
+  private String dbType;
+
+  public String getDbType() {
+    return dbType;
+  }
+
+  public void setDbType(String dbType) {
+    this.dbType = dbType;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/core/JwtToken.java b/src/main/java/com/supwisdom/dlpay/framework/core/JwtToken.java
new file mode 100644
index 0000000..072ea5d
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/framework/core/JwtToken.java
@@ -0,0 +1,39 @@
+package com.supwisdom.dlpay.framework.core;
+
+import org.jose4j.jwt.NumericDate;
+
+public class JwtToken {
+  private String jti;
+  private NumericDate expiration;
+  private String jwtToken;
+
+  public JwtToken(String jti, String jwtToken, NumericDate exp) {
+    this.jti = jti;
+    this.jwtToken = jwtToken;
+    this.expiration = exp;
+  }
+
+  public String getJti() {
+    return jti;
+  }
+
+  public void setJti(String jti) {
+    this.jti = jti;
+  }
+
+  public String getJwtToken() {
+    return jwtToken;
+  }
+
+  public void setJwtToken(String jwtToken) {
+    this.jwtToken = jwtToken;
+  }
+
+  public NumericDate getExpiration() {
+    return expiration;
+  }
+
+  public void setExpiration(NumericDate expiration) {
+    this.expiration = expiration;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java b/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java
index 20344ab..9c0a35b 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java
@@ -5,6 +5,7 @@
 import org.jose4j.jws.AlgorithmIdentifiers;
 import org.jose4j.jws.JsonWebSignature;
 import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
 import org.jose4j.jwt.consumer.InvalidJwtException;
 import org.jose4j.jwt.consumer.JwtConsumer;
 import org.jose4j.jwt.consumer.JwtConsumerBuilder;
@@ -26,7 +27,7 @@
     return jwtConfig.getHeader();
   }
 
-  public String generateToken(Map<String, Object> params) throws JoseException {
+  public JwtToken generateToken(Map<String, Object> params) throws JoseException, MalformedClaimException {
     JwtClaims claims = new JwtClaims();
     claims.setIssuer(params.get("issuer").toString());  // who creates the token and signs it
     if (params.get("audience") != null) {
@@ -57,16 +58,16 @@
     jws.setKey(key.getKey());
     jws.setKeyIdHeaderValue(key.getKeyId());
     jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
-    return jws.getCompactSerialization();
+    return new JwtToken(claims.getJwtId(), jws.getCompactSerialization(), claims.getExpirationTime());
   }
 
-  public String generateToken(UserDetails userDetails) throws JoseException {
+  public JwtToken generateToken(UserDetails userDetails) throws JoseException, MalformedClaimException {
     Map<String, Object> claims = new HashMap<>();
     claims.put("uid", userDetails.getUsername());
     return generateToken(claims);
   }
 
-  public Map<String, List<Object>> verifyToken(String token) throws JoseException, InvalidJwtException {
+  public Map<String, Object> verifyToken(String token) throws JoseException, InvalidJwtException {
     Map<String, Object> keySpec = new HashMap<>();
     keySpec.put("kty", "oct");
     keySpec.put("k", jwtConfig.getSecret());
@@ -74,9 +75,8 @@
     JwtConsumer jwtConsumer = new JwtConsumerBuilder()
         .setRequireExpirationTime() // the JWT must have an expiration time
         .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
-        .setRequireSubject() // the JWT must have a subject claim
-        .setExpectedIssuer("supwisdom") // whom the JWT needs to have been issued by
         .setVerificationKey(key.getKey()) // verify the signature with the public key
+        .setSkipDefaultAudienceValidation()
         .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
             new AlgorithmConstraints(org.jose4j.jwa.AlgorithmConstraints.ConstraintType.WHITELIST, // which is only RS256 here
                 AlgorithmIdentifiers.HMAC_SHA256))
@@ -84,24 +84,6 @@
 
     //  Validate the JWT and process it to the Claims
     JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
-    System.out.println("JWT validation succeeded! " + jwtClaims);
-    return jwtClaims.flattenClaims();
-    // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
-    // Hopefully with meaningful explanations(s) about what went wrong.
-
-//      // Programmatic access to (some) specific reasons for JWT invalidity is also possible
-//      // should you want different error handling behavior for certain conditions.
-//
-//      // Whether or not the JWT has expired being one common reason for invalidity
-//      if (e.hasExpired())
-//      {
-//        System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
-//      }
-//
-//      // Or maybe the audience was invalid
-//      if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
-//      {
-//        System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
-//      }
+    return jwtClaims.getClaimsMap();
   }
 }
diff --git a/src/main/java/com/supwisdom/dlpay/framework/dao/TaskLockDao.java b/src/main/java/com/supwisdom/dlpay/framework/dao/TaskLockDao.java
index 6c36c5d..576c089 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/dao/TaskLockDao.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/dao/TaskLockDao.java
@@ -21,12 +21,14 @@
   @Query(value = "select  to_char(sysdate,'yyyymmdd') as hostdate,to_char(sysdate,'hh24miss') as hosttime,to_char(sysdate,'yyyymmddhh24miss') as hostdatetime, sysdate from dual", nativeQuery = true)
   SystemDateTime getOracleDatetime();
 
+  @Query(value = "select  to_char(CURRENT_TIMESTAMP,'yyyymmdd') as hostdate," + "" +
+      "to_char(CURRENT_TIMESTAMP,'hh24miss') as hosttime,to_char(CURRENT_TIMESTAMP,'yyyymmddhh24miss') as hostdatetime," +
+      " CURRENT_TIMESTAMP as sysdate", nativeQuery = true)
+  SystemDateTime getPGDatetime();
+
   @Query(value = " select to_char(sysdate,'yyyyMMddhh24miss')||to_char(SEQ_REFNO.nextval,'FM000000') as billno from dual ", nativeQuery = true)
   String getOracleRefno();
 
-
-
-
   //================= database=PG =================//
   @Query(value = " select to_char(CURRENT_TIMESTAMP,'yyyyMMddhh24miss')||to_char(nextval('SEQ_REFNO'),'FM000000') as billno ", nativeQuery = true)
   String getPgRefno();
diff --git a/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java b/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java
index 2cb43eb..d7bb016 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java
@@ -1,8 +1,8 @@
 package com.supwisdom.dlpay.framework.domain;
 
+import org.springframework.data.annotation.Id;
 import org.springframework.data.redis.core.RedisHash;
 
-import javax.persistence.Id;
 
 @RedisHash("app_client")
 public class ApiClientRedis {
diff --git a/src/main/java/com/supwisdom/dlpay/framework/domain/DateItem.java b/src/main/java/com/supwisdom/dlpay/framework/domain/DateItem.java
deleted file mode 100644
index f4aa56e..0000000
--- a/src/main/java/com/supwisdom/dlpay/framework/domain/DateItem.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.supwisdom.dlpay.framework.domain;
-
-import javax.persistence.*;
-import java.sql.Date;
-
-@Entity
-public class DateItem {
-  private Date date;
-
-  /**
-   * @return the date
-   */
-  @Id
-  @Column(name = "DATE_VALUE")
-  public Date getDate() {
-    return date;
-  }
-
-  /**
-   * @param date
-   *            the date to set
-   */
-  public void setDate(Date date) {
-    this.date = date;
-  }
-}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/domain/JwtRedis.java b/src/main/java/com/supwisdom/dlpay/framework/domain/JwtRedis.java
new file mode 100644
index 0000000..6064748
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/framework/domain/JwtRedis.java
@@ -0,0 +1,39 @@
+package com.supwisdom.dlpay.framework.domain;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.redis.core.RedisHash;
+
+
+@RedisHash("api_jwt")
+public class JwtRedis {
+  @Id
+  String jti;
+
+  String status;
+
+  Long expiration;
+
+  public String getJti() {
+    return jti;
+  }
+
+  public void setJti(String jti) {
+    this.jti = jti;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public Long getExpiration() {
+    return expiration;
+  }
+
+  public void setExpiration(Long expiration) {
+    this.expiration = expiration;
+  }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/domain/PersonRedis.java b/src/main/java/com/supwisdom/dlpay/framework/domain/PersonRedis.java
index 2c25864..623e55a 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/domain/PersonRedis.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/domain/PersonRedis.java
@@ -1,9 +1,8 @@
 package com.supwisdom.dlpay.framework.domain;
 
+import org.springframework.data.annotation.Id;
 import org.springframework.data.redis.core.RedisHash;
 
-import javax.persistence.Id;
-
 @RedisHash("person")
 public class PersonRedis {
   @Id
diff --git a/src/main/java/com/supwisdom/dlpay/framework/redisrepo/ApiJwtRepository.java b/src/main/java/com/supwisdom/dlpay/framework/redisrepo/ApiJwtRepository.java
new file mode 100644
index 0000000..38a3fbe
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/framework/redisrepo/ApiJwtRepository.java
@@ -0,0 +1,7 @@
+package com.supwisdom.dlpay.framework.redisrepo;
+
+import com.supwisdom.dlpay.framework.domain.JwtRedis;
+import org.springframework.data.repository.CrudRepository;
+
+public interface ApiJwtRepository extends CrudRepository<JwtRedis, String> {
+}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/service/impl/SystemUtilServiceImpl.java b/src/main/java/com/supwisdom/dlpay/framework/service/impl/SystemUtilServiceImpl.java
index 0cf7bcf..07d5e63 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/service/impl/SystemUtilServiceImpl.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/service/impl/SystemUtilServiceImpl.java
@@ -1,10 +1,10 @@
 package com.supwisdom.dlpay.framework.service.impl;
 
+import com.supwisdom.dlpay.framework.core.DatabaseConfig;
 import com.supwisdom.dlpay.framework.dao.SettleCtlDao;
 import com.supwisdom.dlpay.framework.dao.TaskLockDao;
 import com.supwisdom.dlpay.framework.dao.TranscodeDao;
 import com.supwisdom.dlpay.framework.data.SystemDateTime;
-import com.supwisdom.dlpay.framework.domain.DateItem;
 import com.supwisdom.dlpay.framework.domain.TSettlectl;
 import com.supwisdom.dlpay.framework.domain.TTaskLock;
 import com.supwisdom.dlpay.framework.domain.TTranscode;
@@ -15,18 +15,13 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.Query;
-import javax.persistence.criteria.CriteriaBuilder;
-import java.sql.SQLException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 @Service
 public class SystemUtilServiceImpl implements SystemUtilService {
-  @PersistenceContext
-  EntityManager em;
+  @Autowired
+  private DatabaseConfig databaseConfig;
 
   @Autowired
   private TaskLockDao taskLockDao;
@@ -70,17 +65,21 @@
    * 获取oracle数据库时间
    */
   private SystemDateTime getOracleDatetime() {
-//    return taskLockDao.getOracleDatetime();
-    String sql = "SELECT CURRENT_TIMESTAMP AS DATE_VALUE";
-    Query query = em.createNativeQuery(sql, DateItem.class);
-    DateItem dateItem = (DateItem) query.getSingleResult();
-    SystemDateTime now = new SystemDateTimeImpl(dateItem.getDate());
-    return now;
+    return taskLockDao.getOracleDatetime();
+  }
+
+  private SystemDateTime getPGDatetime() {
+    return taskLockDao.getPGDatetime();
   }
 
   @Override
   public SystemDateTime getSysdatetime() {
-    return getOracleDatetime();
+    switch (databaseConfig.getDbType()) {
+      case "postgresql":
+        return getPGDatetime();
+      default:
+        return getOracleDatetime();
+    }
   }
 
   @Override
diff --git a/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java b/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java
index 5596b24..6b45836 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java
@@ -12,6 +12,12 @@
   public static final String STATUS_LOCKED = "locked";
 
   /**
+   * JWT 状态
+   */
+  public static final String JWT_STATUS_NORMAL = "normal";
+  public static final String JWT_STATUS_BLACKLIST = "blacklist";
+
+  /**
    * 流水状态
    * temp -- 临时流水
    * init -- 初始化流水
@@ -35,20 +41,20 @@
 
   /**
    * 支付方式
-   * */
-  public static final String PAYTYPE_CASH="cash";
-  public static final String PAYTYPE_BALANCE="balance";
+   */
+  public static final String PAYTYPE_CASH = "cash";
+  public static final String PAYTYPE_BALANCE = "balance";
 
 
   /**
    * feetype
    * - 消费:折扣、搭伙费(管理费)
    * - 充值:优惠、服务费(手续费)
-   * */
+   */
 
-  public static final String FEETYPE_CONSUME_MEALER="mealer"; //收管理费
-  public static final String FEETYPE_CONSUME_DISCOUNT="discount"; //折扣款抵扣
+  public static final String FEETYPE_CONSUME_MEALER = "mealer"; //收管理费
+  public static final String FEETYPE_CONSUME_DISCOUNT = "discount"; //折扣款抵扣
 
-  public static final String PAYTYPE_RECHARGE_COUPON="coupon";  // 充值优惠
-  public static final String PAYTYPE_RECHARGE_SERVICEFEE="servicefee"; //收服务费
+  public static final String PAYTYPE_RECHARGE_COUPON = "coupon";  // 充值优惠
+  public static final String PAYTYPE_RECHARGE_SERVICEFEE = "servicefee"; //收服务费
 }
diff --git a/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt b/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
index 5bcea3a..521fe7c 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
@@ -7,12 +7,13 @@
 import com.supwisdom.dlpay.framework.security.validate.ImageCodeUtil
 import com.supwisdom.dlpay.framework.security.validate.VerifyCode
 import com.supwisdom.dlpay.framework.domain.ApiClientRedis
+import com.supwisdom.dlpay.framework.domain.JwtRedis
 import com.supwisdom.dlpay.framework.domain.TOperator
 import com.supwisdom.dlpay.framework.redisrepo.ApiClientRepository
-import com.supwisdom.dlpay.framework.security.OperUtil
-import com.supwisdom.dlpay.framework.service.OperatorDetailService
+import com.supwisdom.dlpay.framework.redisrepo.ApiJwtRepository
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.HmacUtil
+import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.system.service.CommonService
 import com.supwisdom.dlpay.system.service.FunctionService
 import mu.KotlinLogging
@@ -38,10 +39,13 @@
 class ApiAuthController {
 
     @Autowired
-    lateinit var repo: ApiClientRepository
+    lateinit var apiClientRepository: ApiClientRepository
 
     @Autowired
-    lateinit var apiClient: ApiClientDao
+    lateinit var apiJwtRepository: ApiJwtRepository
+
+    @Autowired
+    lateinit var apiClientDao: ApiClientDao
 
     @Autowired
     lateinit var systemUtil: SystemUtilService
@@ -49,9 +53,9 @@
     @Autowired
     lateinit var jwtConfig: JwtConfig
 
-    @GetMapping("/gettoken")
-    fun loginInit(appid: String): ResponseEntity<Any> {
-        apiClient.findById(appid).run {
+    @GetMapping(value = ["/gettoken", "/gettoken/{clientid}"])
+    fun loginInit(appid: String, @PathVariable clientid: String?): ResponseEntity<Any> {
+        apiClientDao.findById(appid).run {
             if (!isPresent) {
                 return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
             }
@@ -64,12 +68,12 @@
             val token = generateRandomToken()
             val now = systemUtil.sysdatetime.hostdatetime
             ApiClientRedis().apply {
-                id = appid
+                id = if (clientid == null) appid else "$appid-$clientid"
                 loginTimestamp = now
                 roles = it.roles
                 this.token = HmacUtil.HMACSHA256(token, it.secret)
             }.also {
-                repo.save(it)
+                apiClientRepository.save(it)
             }
             return ResponseEntity.ok(ResponseBodyBuilder.create()
                     .data("token", token)
@@ -88,20 +92,25 @@
         return (api.token == secret)
     }
 
-    @GetMapping("/authentication")
-    fun login(appid: String, secret: String): ResponseEntity<Any> {
-        return repo.findById(appid).let {
+    @GetMapping(value = ["/authentication", "/authentication/{clientid}"])
+    fun login(appid: String, secret: String, @PathVariable clientid: String?): ResponseEntity<Any> {
+        val requestId = if (clientid == null) appid else "$appid-$clientid"
+        return apiClientRepository.findById(requestId).let {
             if (it.isPresent && checkSecretToken(it.get(), secret)) {
-                it.get().also { client ->
-                    client.token = ""
-                    repo.save(client)
-                }
+                apiClientRepository.delete(it.get())
                 val token = JwtTokenUtil(jwtConfig).generateToken(
-                        mapOf("uid" to appid, "issuer" to "supwisdom",
-                                "subject" to "payapi",
+                        mapOf("uid" to appid, "issuer" to "payapi",
+                                "audience" to (clientid ?: appid),
                                 "authorities" to it.get().roles.split(";")))
+                JwtRedis().apply {
+                    jti = token.jti
+                    status = TradeDict.JWT_STATUS_NORMAL
+                    expiration = token.expiration.value
+                }.apply {
+                    apiJwtRepository.save(this)
+                }
                 ResponseEntity.ok(ResponseBodyBuilder.create()
-                        .data("jwt", token)
+                        .data("jwt", token.jwtToken)
                         .data("appid", appid)
                         .success())
             } else {
diff --git a/src/main/kotlin/com/supwisdom/dlpay/security.kt b/src/main/kotlin/com/supwisdom/dlpay/security.kt
index 491cc46..abb1dd7 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/security.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/security.kt
@@ -3,9 +3,12 @@
 import com.supwisdom.dlpay.framework.core.JwtConfig
 import com.supwisdom.dlpay.framework.core.JwtTokenUtil
 import com.supwisdom.dlpay.framework.core.PasswordBCryptConfig
+import com.supwisdom.dlpay.framework.redisrepo.ApiJwtRepository
 import com.supwisdom.dlpay.framework.security.ValidateCodeSecurityConfig
 import com.supwisdom.dlpay.framework.service.OperatorDetailService
+import com.supwisdom.dlpay.framework.util.TradeDict
 import org.jose4j.jwt.consumer.InvalidJwtException
+import org.jose4j.lang.JoseException
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
@@ -26,6 +29,7 @@
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
 import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher
+import org.springframework.stereotype.Component
 import org.springframework.web.filter.OncePerRequestFilter
 import java.security.SecureRandom
 import javax.servlet.FilterChain
@@ -34,18 +38,50 @@
 import javax.sql.DataSource
 
 
-class ApiJwtAuthenticationFilter(jwt: JwtTokenUtil) : OncePerRequestFilter() {
-    private val jwtUtil = jwt
+@Component
+class ApiJwtAuthenticationFilter : OncePerRequestFilter() {
+    @Autowired
+    lateinit var jwtConfig: JwtConfig
+
+    @Autowired
+    lateinit var apiJwtRepository: ApiJwtRepository
+
+    private var jwtUtil: JwtTokenUtil? = null
+
+
+    private fun getUtil(): JwtTokenUtil {
+        if (jwtUtil == null) {
+            jwtUtil = JwtTokenUtil((jwtConfig))
+        }
+        return jwtUtil as JwtTokenUtil
+    }
 
     override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
-        request.getHeader(jwtUtil.header)?.let { jwt ->
+        request.getHeader(jwtConfig.header)?.let { jwt ->
             try {
-                val claims = jwtUtil.verifyToken(jwt)
+                val claims = getUtil().verifyToken(jwt)
+                apiJwtRepository.findById(claims["jti"].toString()).let {
+                    if (!it.isPresent) {
+                        throw JoseException("JWT has not been register")
+                    }
+                    if (it.get().status != TradeDict.JWT_STATUS_NORMAL) {
+                        throw JoseException("JWT status error : ${it.get().status}")
+                    }
+                }
                 val auth = UsernamePasswordAuthenticationToken(claims["uid"], null,
-                        claims["authorities"]?.map { SimpleGrantedAuthority(it as String) })
+                        (claims["authorities"] as ArrayList<*>)
+                                .map { SimpleGrantedAuthority(it as String) })
                 SecurityContextHolder.getContext().authentication = auth
             } catch (e: InvalidJwtException) {
-                SecurityContextHolder.clearContext();
+                SecurityContextHolder.clearContext()
+                if (e.hasExpired()) {
+                    apiJwtRepository.deleteById(e.jwtContext.jwtClaims.jwtId)
+                    response.sendError(HttpStatus.UNAUTHORIZED.value(), e.message)
+                } else {
+                    response.sendError(HttpStatus.BAD_REQUEST.value(), e.message)
+                }
+            } catch (e: JoseException) {
+                SecurityContextHolder.clearContext()
                 response.sendError(HttpStatus.BAD_REQUEST.value(), e.message)
             }
         }
@@ -62,13 +98,13 @@
         class ApiWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
 
             @Autowired
-            lateinit var jwtConfig: JwtConfig
+            lateinit var apiFilter: ApiJwtAuthenticationFilter
 
             override fun configure(http: HttpSecurity) {
                 // 设置 API 访问权限管理
                 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                         .and()
-                        .addFilterAfter(ApiJwtAuthenticationFilter(JwtTokenUtil(jwtConfig)),
+                        .addFilterAfter(apiFilter,
                                 UsernamePasswordAuthenticationFilter::class.java)
                         .antMatcher("/api/**")
                         .authorizeRequests()