From dd61345dcbc7606312713edd76cdc13bc0c44411 Mon Sep 17 00:00:00 2001 From: Tang Cheng Date: Wed, 31 Jul 2019 17:20:15 +0800 Subject: [PATCH] =?utf8?q?chore:=20=E4=BC=98=E5=8C=96=20filter=20=E5=88=A4?= =?utf8?q?=E6=96=AD=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6=20fix:=20=E5=A2=9E?= =?utf8?q?=E5=8A=A0=E5=A4=9A=E7=A7=9F=E6=88=B7=E7=9A=84=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../dlpay/framework/core/JwtConfig.java | 13 ++- .../dlpay/framework/util/Constants.java | 1 + .../com/supwisdom/dlpay/PayApiApplication.kt | 28 ++++++ .../com/supwisdom/dlpay/mobile/MobileApi.kt | 41 +++++++-- .../kotlin/com/supwisdom/dlpay/security.kt | 92 ++++++++----------- 5 files changed, 111 insertions(+), 64 deletions(-) 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 fbaa21c9..85167fdf 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 @@ public class JwtConfig { @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 class JwtConfig { 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 46af8bbb..0bf11d8f 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 @@ package com.supwisdom.dlpay.framework.util; 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 1cc6089b..8b7b7967 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.SpringBootApplication 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 @@ -66,6 +67,33 @@ class AppConfig { } } +@Configuration +class JwtAuthFilterRegistration { + @Autowired + private lateinit var apiJwtAuthenticationFilter: ApiJwtAuthenticationFilter + + @Autowired + private lateinit var mobileSecurityFilter: MobileSecurityFilter + + @Bean + fun registerJwtFilter(): FilterRegistrationBean { + val registration = FilterRegistrationBean() + registration.filter = apiJwtAuthenticationFilter + registration.addUrlPatterns("/api/*") + registration.order = 1 + return registration + } + + @Bean + fun registerMobileFilter(): FilterRegistrationBean { + val registrationBean = FilterRegistrationBean() + registrationBean.filter = mobileSecurityFilter + registrationBean.order = 2 + registrationBean.addUrlPatterns("/mobile/*") + return registrationBean + } +} + @Configuration class HttpSessionConfig { @Bean 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 3d7471c7..3e75d864 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.mobile.service.MobileApiService 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 @@ class ApiInit { 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 @@ class ApiV1 { @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 { + 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 @@ class ApiV1 { 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 @@ class ApiV1 { ?: 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 @@ class ApiV1 { 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 @@ class ApiV1 { 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 @@ class ApiV1 { 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 2e747aea..15911916 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.redisrepo.ApiJwtRepository 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.beans.factory.annotation.Autowired 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.config.http.SessionCreationPolicy 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 org.springframework.web.filter.OncePerRequestFilter 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 @@ class ApiJwtAuthenticationFilter : OncePerRequestFilter() { 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 @@ class ApiJwtAuthenticationFilter : OncePerRequestFilter() { } 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 @@ class ApiJwtAuthenticationFilter : OncePerRequestFilter() { } 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 @@ class ApiJwtAuthenticationFilter : OncePerRequestFilter() { 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 @@ class ApiJwtAuthenticationFilter : OncePerRequestFilter() { 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 @@ class ApiJwtAuthenticationFilter : OncePerRequestFilter() { } } +@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 @@ class WebSecurityConfig { @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 @@ class WebSecurityConfig { .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 @@ class WebSecurityConfig { .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 } -- 2.17.1