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
}