chore: 优化 filter 判断处理机制
fix: 增加多租户的开关
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/core/JwtConfig.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/core/JwtConfig.java
index fbaa21c..85167fd 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/core/JwtConfig.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/core/JwtConfig.java
@@ -12,7 +12,10 @@
   @Value("${jwt.header:Authorization}")
   private String header = "Authorization";
   @Value("${jwt.token_header:Bearer }")
-  private String tokenHeader = "Bearer";
+  private String tokenHeader = "Bearer ";
+
+  @Value("${jwt.multitenant:false}")
+  private Boolean multiTenant = false;
 
   public String getSecret() {
     return secret;
@@ -33,4 +36,12 @@
   public void setExpiration(Long expiration) {
     this.expiration = expiration;
   }
+
+  public Boolean getMultiTenant() {
+    return multiTenant;
+  }
+
+  public void setMultiTenant(Boolean multiTenant) {
+    this.multiTenant = multiTenant;
+  }
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java
index 46af8bb..0bf11d8 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/Constants.java
@@ -3,6 +3,7 @@
 public class Constants {
   //  HTTP HEADER define
   public static final String HEADER_TETANTID = "X-TENANT-ID";
+  public static final String DEFAULT_TENANTID = "{tenantid}";
 
   // define
   public static final String JWT_CLAIM_TENANTID = "tenantId";
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
index 1cc6089..8b7b796 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
@@ -11,6 +11,7 @@
 import org.springframework.boot.autoconfigure.data.redis.RedisProperties
 import org.springframework.boot.builder.SpringApplicationBuilder
 import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.boot.web.servlet.FilterRegistrationBean
 import org.springframework.boot.web.servlet.ServletComponentScan
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
 import org.springframework.cache.annotation.EnableCaching
@@ -67,6 +68,33 @@
 }
 
 @Configuration
+class JwtAuthFilterRegistration {
+    @Autowired
+    private lateinit var apiJwtAuthenticationFilter: ApiJwtAuthenticationFilter
+
+    @Autowired
+    private lateinit var mobileSecurityFilter: MobileSecurityFilter
+
+    @Bean
+    fun registerJwtFilter(): FilterRegistrationBean<ApiJwtAuthenticationFilter> {
+        val registration = FilterRegistrationBean<ApiJwtAuthenticationFilter>()
+        registration.filter = apiJwtAuthenticationFilter
+        registration.addUrlPatterns("/api/*")
+        registration.order = 1
+        return registration
+    }
+
+    @Bean
+    fun registerMobileFilter(): FilterRegistrationBean<MobileSecurityFilter> {
+        val registrationBean = FilterRegistrationBean<MobileSecurityFilter>()
+        registrationBean.filter = mobileSecurityFilter
+        registrationBean.order = 2
+        registrationBean.addUrlPatterns("/mobile/*")
+        return registrationBean
+    }
+}
+
+@Configuration
 class HttpSessionConfig {
     @Bean
     fun sessionRedisTemplate(
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
index 3d7471c..3e75d86 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -18,12 +18,17 @@
 import com.supwisdom.dlpay.system.service.DictionaryProxy
 import com.supwisdom.dlpay.util.ConstantUtil
 import org.apache.commons.lang.StringUtils
+import org.jose4j.jwt.ReservedClaimNames
+import org.jose4j.lang.JoseException
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.data.redis.core.RedisTemplate
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
 import org.springframework.security.core.GrantedAuthority
 import org.springframework.security.core.authority.AuthorityUtils
 import org.springframework.security.core.context.SecurityContextHolder
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.web.bind.annotation.RequestHeader
 import org.springframework.web.bind.annotation.RequestMapping
 import org.springframework.web.bind.annotation.RestController
 import org.springframework.web.bind.annotation.RequestParam
@@ -90,8 +95,8 @@
         val temp = redisTemplate.opsForValue().get(phone)
         if (temp.isNullOrEmpty()) {
             val code = RandomUtils.randomNumber(6)
-            val rs = mobileApiService.sendSms(phone,code)
-            if("0"!=rs.retcode){
+            val rs = mobileApiService.sendSms(phone, code)
+            if ("0" != rs.retcode) {
                 return JsonResult.error(rs.retmsg)
             }
             redisTemplate.opsForValue().set(phone, code, Duration.ofMinutes(5))
@@ -230,12 +235,29 @@
     @Autowired
     lateinit var citizencardPayService: CitizencardPayService
 
+    @Autowired
+    lateinit var apiJwtRepository: ApiJwtRepository
+    @Autowired
+    lateinit var jwtConfig: JwtConfig
+
     @RequestMapping("/idtypes")
     fun idtypes(): JsonResult {
         var dict = dictionaryProxy.getDictionaryAsMap(Dictionary.IDTYPE)
         return JsonResult.ok("OK").put("idtypes", dict)!!
     }
 
+    @RequestMapping("/logout")
+    fun logout(@RequestHeader("Authorization") auth: String?): ResponseEntity<Any> {
+        if (auth == null) {
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+        }
+        val jwt = auth.substring(jwtConfig.tokenHeader.length)
+        val claims = JwtTokenUtil(jwtConfig).verifyToken(jwt)
+        SecurityContextHolder.clearContext()
+        apiJwtRepository.deleteById(claims[ReservedClaimNames.JWT_ID].toString())
+        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+    }
+
     /**
      * 用户信息
      * */
@@ -260,8 +282,8 @@
         val temp = redisTemplate.opsForValue().get(user.phone)
         if (temp.isNullOrEmpty()) {
             val code = RandomUtils.randomNumber(6)
-            val rs = mobileApiService.sendSms(user.phone,code)
-            if("0"!=rs.retcode){
+            val rs = mobileApiService.sendSms(user.phone, code)
+            if ("0" != rs.retcode) {
                 return JsonResult.error(rs.retmsg)
             }
             redisTemplate.opsForValue().set(user.phone, code, Duration.ofMinutes(5))
@@ -461,7 +483,8 @@
                 ?: return JsonResult.error("用户不存在,请注册")
         var signed: String
         if (!user.userid.isNullOrEmpty()) {
-            var card = mobileApiService.findCardByUserid(user.userid!!) ?: return JsonResult.error("卡片不存在,请重新绑定")
+            var card = mobileApiService.findCardByUserid(user.userid!!)
+                    ?: return JsonResult.error("卡片不存在,请重新绑定")
             if (card.signed) {
                 signed = TradeDict.STATUS_YES
             } else {
@@ -612,7 +635,8 @@
             return JsonResult.error(-1, "银行卡未绑定,请先绑定")
         }
         val person = userService.findOnePersonByUserid(user.userid!!)
-        val card = mobileApiService.findCardByUserid(user.userid!!) ?: return JsonResult.error(-1, "银行卡未绑定,请先绑定")
+        val card = mobileApiService.findCardByUserid(user.userid!!)
+                ?: return JsonResult.error(-1, "银行卡未绑定,请先绑定")
         var status = ""
         //normal/loss/frozen/locked
         when (card.transStatus) {
@@ -652,7 +676,8 @@
                 if (it) mobileApiService.saveUser(user)
             }
         }
-        var card = mobileApiService.findCardByUserid(user.userid!!) ?: return JsonResult.error(-1, "银行卡未绑定,请先绑定")
+        var card = mobileApiService.findCardByUserid(user.userid!!)
+                ?: return JsonResult.error(-1, "银行卡未绑定,请先绑定")
         if (card.transStatus != TradeDict.STATUS_NORMAL) {
             return JsonResult.error("卡状态非正常,不能挂失")
         }
@@ -671,7 +696,7 @@
         mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
         //TODO qrcode
-        return JsonResult.ok("ok").put("qrcode",p.name)!!
+        return JsonResult.ok("ok").put("qrcode", p.name)!!
     }
 
 
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt
index 2e747ae..1591191 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt
@@ -7,6 +7,7 @@
 import com.supwisdom.dlpay.framework.security.MyAuthenticationFailureHandler
 import com.supwisdom.dlpay.framework.security.ValidateCodeSecurityConfig
 import com.supwisdom.dlpay.framework.service.OperatorDetailService
+import com.supwisdom.dlpay.framework.tenant.TenantContext
 import com.supwisdom.dlpay.framework.util.Constants
 import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.mobile.AuthLoginFailHandler
@@ -19,7 +20,6 @@
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.core.annotation.Order
-import org.springframework.data.redis.connection.RedisConnectionFactory
 import org.springframework.http.HttpStatus
 import org.springframework.security.authentication.AuthenticationManager
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
@@ -33,7 +33,6 @@
 import org.springframework.security.core.authority.SimpleGrantedAuthority
 import org.springframework.security.core.context.SecurityContextHolder
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
-import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
 import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher
@@ -45,6 +44,7 @@
 import java.security.SecureRandom
 import java.util.*
 import javax.servlet.FilterChain
+import javax.servlet.http.HttpFilter
 import javax.servlet.http.HttpServletRequest
 import javax.servlet.http.HttpServletResponse
 import javax.sql.DataSource
@@ -60,10 +60,6 @@
 
     private var jwtUtil: JwtTokenUtil? = null
 
-    @Autowired
-    private lateinit var redisConnectionFactory: RedisConnectionFactory
-
-
     private fun getUtil(): JwtTokenUtil {
         if (jwtUtil == null) {
             jwtUtil = JwtTokenUtil((jwtConfig))
@@ -72,22 +68,8 @@
     }
 
     override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
-        var context: String? = request.contextPath
-        if (context == null || "" == context.trim { it <= ' ' }) {
-            context = "/"
-        }
-        if (request.requestURI.isEmpty()) {
-            filterChain.doFilter(request, response)
-            return
-        }
-        var url = request.requestURI
-        if ("/" != context) {
-            url = url.replace(context, "")
-        }
-        logger.info(url)
-        if (!url.startsWith("/api/") && !url.startsWith("/mobileapi/v1/")) {
-            filterChain.doFilter(request, response)
-            return
+        if (!jwtConfig.multiTenant) {
+            TenantContext.setTenantSchema(Constants.DEFAULT_TENANTID)
         }
         request.getHeader(jwtConfig.header)?.let { authHeader ->
             try {
@@ -96,18 +78,7 @@
                 } else {
                     throw JoseException("JWT Header error")
                 }
-                if (url.contains("/userinfor")) {
-                    SecurityContextHolder.getContext().authentication = RedisTokenStore(redisConnectionFactory).readAuthentication(jwt)
-                    filterChain.doFilter(request, response)
-                    return
-                }
                 val claims = getUtil().verifyToken(jwt)
-                if (url.equals("/mobileapi/v1/logout")) {
-                    SecurityContextHolder.clearContext()
-                    apiJwtRepository.deleteById(claims[ReservedClaimNames.JWT_ID].toString())
-                    throw JoseException("JWT has not been register")
-                }
-
                 apiJwtRepository.findById(claims[ReservedClaimNames.JWT_ID].toString()).let {
                     if (!it.isPresent) {
                         throw JoseException("JWT has not been register")
@@ -117,14 +88,17 @@
                         throw JoseException("JWT status error : ${it.get().status}")
                     }
                 }
-                val tenantId = request.getHeader(Constants.HEADER_TETANTID)
-                if (tenantId == null) {
-                    response.status = HttpStatus.UNAUTHORIZED.value()
-                    return
-                }
-                if (claims[Constants.JWT_CLAIM_TENANTID] != tenantId) {
-                    response.status = HttpStatus.UNAUTHORIZED.value()
-                    return
+                if (jwtConfig.multiTenant) {
+                    val tenantId = request.getHeader(Constants.HEADER_TETANTID)
+                    if (tenantId == null) {
+                        response.status = HttpStatus.UNAUTHORIZED.value()
+                        return
+                    }
+                    if (claims[Constants.JWT_CLAIM_TENANTID] != tenantId) {
+                        response.status = HttpStatus.UNAUTHORIZED.value()
+                        return
+                    }
+                    TenantContext.setTenantSchema(tenantId)
                 }
                 val auth = UsernamePasswordAuthenticationToken(claims[Constants.JWT_CLAIM_UID], null,
                         (claims[Constants.JWT_CLAIM_AUTHORITIES] as ArrayList<*>)
@@ -144,10 +118,10 @@
                 response.status = HttpStatus.UNAUTHORIZED.value()
                 response.contentType = "application/json;charset=UTF-8"
                 return
-            } catch (e:Exception){
+            } catch (e: Exception) {
                 SecurityContextHolder.clearContext()
                 // jwt 失效后返回 401
-                response.status=HttpStatus.UNAUTHORIZED.value()
+                response.status = HttpStatus.UNAUTHORIZED.value()
                 response.contentType = "application/json;charset=UTF-8"
                 return
             }
@@ -156,6 +130,19 @@
     }
 }
 
+@Component
+class MobileSecurityFilter : HttpFilter() {
+    override fun doFilter(req: HttpServletRequest, res: HttpServletResponse, chain: FilterChain) {
+//        val url = req.requestURI
+//        if (url.contains("/userinfor")) {
+//            SecurityContextHolder.getContext().authentication = RedisTokenStore(redisConnectionFactory).readAuthentication(jwt)
+//            chain.doFilter(req, res)
+//            return
+//        }
+        chain.doFilter(req, res)
+    }
+}
+
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
 class WebSecurityConfig {
@@ -165,22 +152,16 @@
         @Order(1)
         class ApiWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
 
-            @Autowired
-            lateinit var apiFilter: ApiJwtAuthenticationFilter
-
             override fun configure(http: HttpSecurity) {
                 // 设置 API 访问权限管理
                 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                         .and()
                         .antMatcher("/api/**")
-                        .addFilterAfter(apiFilter,
-                                UsernamePasswordAuthenticationFilter::class.java)
                         .authorizeRequests()
                         .antMatchers("/api/auth/**").permitAll()
                         .antMatchers("/api/notify/**").permitAll()
                         .antMatchers("/api/common/**").permitAll()
                         .antMatchers("/api/userinfor").hasAnyRole("ADMIN", "THIRD_ADMIN")
-//                        .antMatchers("/api/common/**").hasAnyRole("THIRD_COMMON", "THIRD_ADMIN")
                         .antMatchers("/api/consume/**").hasRole("THIRD_CONSUME")
                         .antMatchers("/api/recharge/**").hasRole("THIRD_DEPOSIT")
                         .antMatchers("/api/user/**").hasRole("THIRD_ADMIN")
@@ -242,7 +223,7 @@
                         .antMatcher("/mobileapi/**")
                         .addFilterAfter(apiFilter,
                                 UsernamePasswordAuthenticationFilter::class.java)
-                        .authorizeRequests().antMatchers("/mobileapi/i/**","/mobileapi/login")
+                        .authorizeRequests().antMatchers("/mobileapi/i/**", "/mobileapi/login")
                         .permitAll().anyRequest().authenticated()
                         .and()
                         .formLogin()
@@ -253,14 +234,15 @@
                         .sessionManagement().maximumSessions(1)
                         .expiredUrl("/mobileapi/sessionexpired")
             }
+
             @Bean
-            fun  corsConfigurationSource(): CorsConfigurationSource {
+            fun corsConfigurationSource(): CorsConfigurationSource {
                 //手机端支持跨域请求
                 val configuration = CorsConfiguration()
-                configuration.allowedOrigins = Arrays.asList("*")
-                configuration.allowedMethods = Arrays.asList("GET","POST")
-                configuration.allowedHeaders = Arrays.asList("*")
-                val source =  UrlBasedCorsConfigurationSource()
+                configuration.allowedOrigins = listOf("*")
+                configuration.allowedMethods = listOf("GET", "POST")
+                configuration.allowedHeaders = listOf("*")
+                val source = UrlBasedCorsConfigurationSource()
                 source.registerCorsConfiguration("/mobileapi/**", configuration);
                 return source
             }