增加OAuth模块
diff --git a/oauth/build.gradle b/oauth/build.gradle
new file mode 100644
index 0000000..17d4252
--- /dev/null
+++ b/oauth/build.gradle
@@ -0,0 +1,90 @@
+plugins {
+    id 'java'
+    id 'org.springframework.boot'
+    id 'org.jetbrains.kotlin.jvm'
+    id 'org.jetbrains.kotlin.plugin.jpa' version '1.3.31'
+    id 'org.jetbrains.kotlin.plugin.spring'
+    id "com.palantir.git-version"
+    id 'com.palantir.docker'
+}
+
+apply plugin: 'java'
+apply plugin: 'io.spring.dependency-management'
+
+def version = gitVersion()
+def details = versionDetails()
+
+group = rootProject.group
+version = '1'
+sourceCompatibility = jdkVersion
+def startClass = 'com.supwisdom.oauth.OAuthApplication'
+
+println("Build version: $version")
+
+bootJar {
+    mainClassName = startClass
+    manifest {
+        attributes('oauth-Version': version)
+    }
+}
+
+jar {
+    baseName = 'oauth'
+}
+
+
+dependencies {
+
+    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+    implementation 'org.springframework.boot:spring-boot-starter-security'
+    implementation 'org.springframework.boot:spring-boot-starter-cache'
+    implementation 'org.springframework.boot:spring-boot-autoconfigure'
+    implementation 'org.springframework.security:spring-security-oauth2-jose'
+    implementation 'org.springframework.security:spring-security-oauth2-client'
+    implementation 'org.springframework.security:spring-security-oauth2-jose'
+    implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.5.RELEASE'
+    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
+    implementation 'org.springframework.session:spring-session-data-redis'
+    implementation 'commons-codec:commons-codec:1.12'
+    implementation 'com.jcabi:jcabi-manifests:1.1'
+    implementation 'org.slf4j:slf4j-parent:1.7.26'
+    implementation 'org.jetbrains.kotlin:kotlin-reflect'
+    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
+    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
+    implementation group: 'com.sun.jersey', name: 'jersey-client', version: '1.19'
+    implementation group: 'javax.servlet', name: 'jstl', version: '1.2'
+    implementation group: 'taglibs', name: 'standard', version: '1.1.2'
+    implementation group: 'commons-codec', name: 'commons-codec', version: '1.6'
+    implementation 'org.dom4j:dom4j:2.1.1'
+    implementation 'commons-beanutils:commons-beanutils:1.9.3'
+    implementation 'commons-net:commons-net:3.6'
+    implementation 'org.postgresql:postgresql:42.2.5'
+    implementation 'net.javacrumbs.shedlock:shedlock-spring:2.5.0'
+    implementation 'net.javacrumbs.shedlock:shedlock-provider-redis-spring:2.5.0'
+    implementation 'org.bitbucket.b_c:jose4j:0.6.5'
+
+    implementation project(':payapi-common')
+
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
+    testImplementation 'io.rest-assured:rest-assured:3.3.0'
+    testImplementation 'io.rest-assured:spring-mock-mvc:3.3.0'
+    testImplementation 'org.hamcrest:hamcrest:2.1'
+
+}
+
+compileKotlin {
+    kotlinOptions {
+        freeCompilerArgs = ['-Xjsr305=strict']
+        jvmTarget = jdkVersion
+    }
+}
+
+compileTestKotlin {
+    kotlinOptions {
+        freeCompilerArgs = ['-Xjsr305=strict']
+        jvmTarget = jdkVersion
+    }
+}
+
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/OAuthApplication.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/OAuthApplication.kt
new file mode 100644
index 0000000..674639b
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/OAuthApplication.kt
@@ -0,0 +1,129 @@
+package com.supwisdom.dlpay
+
+import io.lettuce.core.ReadFrom
+import net.javacrumbs.shedlock.core.LockProvider
+import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.SpringApplication
+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.ServletComponentScan
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
+import org.springframework.cache.annotation.EnableCaching
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.redis.connection.RedisConnectionFactory
+import org.springframework.data.redis.connection.RedisPassword
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration
+import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
+import org.springframework.data.redis.core.RedisTemplate
+import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
+import org.springframework.data.redis.serializer.StringRedisSerializer
+import org.springframework.http.client.SimpleClientHttpRequestFactory
+import org.springframework.scheduling.annotation.EnableScheduling
+import org.springframework.stereotype.Component
+import org.springframework.web.client.RestTemplate
+import java.net.InetSocketAddress
+import java.net.Proxy
+
+
+@Configuration
+@EnableRedisRepositories
+class AppConfig {
+
+    @Autowired
+    private lateinit var redis: RedisProperties
+
+    @Bean
+    fun redisConnectionFactory(): RedisConnectionFactory {
+        val clientConfig = LettuceClientConfiguration.builder()
+                .readFrom(ReadFrom.SLAVE_PREFERRED)
+                .build()
+        val serverConfig = RedisStandaloneConfiguration(redis.host, redis.port)
+        if (redis.password.isNotEmpty()) {
+            serverConfig.password = RedisPassword.of(redis.password)
+        }
+        serverConfig.database = redis.database
+        return LettuceConnectionFactory(serverConfig, clientConfig)
+    }
+
+    @Bean
+    fun lockProvider(connectionFactory: RedisConnectionFactory): LockProvider {
+        return RedisLockProvider(connectionFactory, "prod")
+    }
+}
+
+@Configuration
+class HttpSessionConfig {
+    @Bean
+    fun sessionRedisTemplate(
+            connectionFactory: RedisConnectionFactory): RedisTemplate<Any, Any> {
+        val template = RedisTemplate<Any, Any>()
+        template.keySerializer = StringRedisSerializer()
+        template.hashKeySerializer = StringRedisSerializer()
+
+        template.setDefaultSerializer(GenericJackson2JsonRedisSerializer())
+        template.setConnectionFactory(connectionFactory)
+        return template
+    }
+}
+
+@Configuration
+class RestTemplateConfig {
+    @Component
+    @ConfigurationProperties("resttemplate.proxy")
+    class RestTemplateProxyProperties {
+        @Value("\${type:}")
+        lateinit var type: String
+        @Value("\${host:}")
+        lateinit var host: String
+        @Value("\${port:0}")
+        var port: Int = 0
+    }
+
+    @Bean
+    fun simpleClientHttpRequestFactory(proxyProperties: RestTemplateProxyProperties):
+            SimpleClientHttpRequestFactory {
+        val factory = SimpleClientHttpRequestFactory()
+        factory.setConnectTimeout(15000)
+        factory.setReadTimeout(5000)
+        if (proxyProperties.type.isNotEmpty()) {
+            val proxyType = when (proxyProperties.type) {
+                "http" -> Proxy.Type.HTTP
+                "socks5" -> Proxy.Type.SOCKS
+                else -> Proxy.Type.DIRECT
+            }
+            if (proxyType != Proxy.Type.DIRECT) {
+                factory.setProxy(Proxy(proxyType,
+                        InetSocketAddress(proxyProperties.host, proxyProperties.port)))
+            }
+        }
+        return factory
+    }
+
+    @Bean
+    fun restTemplate(factory: SimpleClientHttpRequestFactory): RestTemplate {
+        return RestTemplate(factory)
+    }
+}
+
+
+@SpringBootApplication
+@EnableScheduling
+@EnableCaching
+@ServletComponentScan
+class OAuthApplication : SpringBootServletInitializer() {
+
+    override fun configure(builder: SpringApplicationBuilder): SpringApplicationBuilder {
+        return builder.sources(OAuthApplication::class.java)
+    }
+}
+
+fun main(args: Array<String>) {
+    SpringApplication.run(OAuthApplication::class.java, * args)
+}
\ No newline at end of file
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/dao/ApiClientDao.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/dao/ApiClientDao.kt
new file mode 100644
index 0000000..cf0bdbe
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/dao/ApiClientDao.kt
@@ -0,0 +1,19 @@
+package com.supwisdom.oauth.dao
+
+import com.supwisdom.oauth.domain.TApiClient
+import org.springframework.data.domain.Page
+import org.springframework.data.domain.Pageable
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+import org.springframework.transaction.annotation.Propagation
+import org.springframework.transaction.annotation.Transactional
+
+@Repository
+interface ApiClientDao : JpaRepository<TApiClient, String> {
+
+    @Transactional(propagation = Propagation.REQUIRED, readOnly = true, rollbackFor = [Exception::class])
+    fun findByAppid(appid: String): TApiClient
+
+    @Transactional(propagation = Propagation.REQUIRED, readOnly = true, rollbackFor = [Exception::class])
+    fun findByAppidContaining(appid: String, pageable: Pageable): Page<TApiClient>
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/dao/OAuthUserDao.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/dao/OAuthUserDao.kt
new file mode 100644
index 0000000..4355fcf
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/dao/OAuthUserDao.kt
@@ -0,0 +1,12 @@
+package com.supwisdom.oauth.dao
+
+import com.supwisdom.oauth.domain.OAuthUser
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface OAuthUserDao : JpaRepository<OAuthUser, String> {
+    fun findByLoginid(loginid: String): OAuthUser?
+}
+
+
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/domain/OAuthUser.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/domain/OAuthUser.kt
new file mode 100644
index 0000000..481c46a
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/domain/OAuthUser.kt
@@ -0,0 +1,177 @@
+package com.supwisdom.oauth.domain
+
+import org.hibernate.annotations.GenericGenerator
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+
+import javax.persistence.*
+import javax.validation.constraints.NotNull
+
+@Entity
+@Table(name = "TB_OAUTH_USER", indexes = [Index(name = "oauth_user_loginid_idx", columnList = "loginid", unique = true)])
+class OAuthUser : UserDetails {
+
+    override fun getAuthorities(): Collection<GrantedAuthority>? {
+        return this.auths
+    }
+
+    override fun isEnabled(): Boolean {
+        return "normal" != this.status
+    }
+
+    override fun getUsername(): String {
+        return this.loginid
+    }
+
+    override fun isCredentialsNonExpired(): Boolean {
+        return true
+    }
+
+    override fun getPassword(): String {
+        return this.loginpwd
+    }
+
+    override fun isAccountNonExpired(): Boolean {
+        return true
+    }
+
+    override fun isAccountNonLocked(): Boolean {
+        return "lock" != this.status
+    }
+
+    @Transient
+    var auths: Collection<GrantedAuthority>? = null
+
+    @Id
+    @GenericGenerator(name = "idGenerator", strategy = "uuid")
+    @GeneratedValue(generator = "idGenerator")
+    @Column(name = "uid", nullable = false, length = 32)
+    @NotNull
+    var uid: String = ""
+
+    @Column(name = "loginid", length = 64)
+    @NotNull
+    var loginid: String = ""
+
+    @Column(name = "loginpwd", length = 64)
+    @NotNull
+    var loginpwd: String = ""
+
+    @Column(name = "status", length = 10)
+    @NotNull
+    var status: String? = null
+
+    /**
+     * 最后登录时间
+     * */
+    @Column(name = "lastlogin", length = 16)
+    var lastlogin: String? = null
+
+    /**
+     * 最后登录手机类型
+     * */
+    @Column(name = "lastloginplatform", length = 100)
+    var lastloginplatform: String? = null
+
+    /**
+     * 支付密码
+     * */
+    @Column(name = "paypwd", length = 64)
+    var paypwd: String? = null
+
+    /**
+     * 登录密码错误次数
+     * */
+    @Column(name = "loginpwderror", length = 4)
+    var loginpwderror: Int = 0
+
+    /**
+     * 登录密码错误时间
+     * */
+    @Column(name = "loginpwderrortime", length = 16)
+    var loginpwderrortime: Long? = 0
+
+    /**
+     * 支付密码错误次数
+     * */
+    @Column(name = "paypwderror", length = 4)
+    var paypwderror: Int = 0
+
+    /**
+     * 支付密码错误时间
+     * */
+    @Column(name = "paypwderrortime", length = 16)
+    var paypwderrortime: Long? = 0
+
+    /**
+     * 关联tb_person
+     * */
+    @Column(name = "userid", length = 32)
+    var userid: String? = null
+
+    /**
+     * jti
+     * */
+    @Column(name = "jti", length = 64)
+    var jti: String? = null
+
+    fun checkLoginpwdtime():Int{
+        if (this.loginpwderror >= 5 && (System.currentTimeMillis() - this.loginpwderrortime!!) < 1000 * 60 * 30) {
+            return -1
+        } else if (this.loginpwderror >= 5 && (System.currentTimeMillis() - this.loginpwderrortime!!) > 1000 * 60 * 30) {
+            //更新时间
+            this.loginpwderror = 0
+            this.loginpwderrortime = null
+            return 1
+        }
+        return 0
+    }
+
+    fun updateLoginpwderror(ok: Boolean): Boolean {
+        return if (ok) {
+            if (this.loginpwderror > 0) {
+                this.loginpwderror = 0
+                this.loginpwderrortime = null
+                true
+            } else {
+                false
+            }
+        } else {
+            if (this.loginpwderror == 0) {
+                this.loginpwderrortime = System.currentTimeMillis()
+            }
+            this.loginpwderror += 1
+            true
+        }
+    }
+
+    fun checkPaypwdtime():Int{
+        if (this.paypwderror >= 5 && (System.currentTimeMillis() - this.paypwderrortime!!) < 1000 * 60 * 30) {
+            return -1
+        } else if (this.paypwderror >= 5 && (System.currentTimeMillis() - this.paypwderrortime!!) > 1000 * 60 * 30) {
+            //更新时间
+            this.paypwderror = 0
+            this.paypwderrortime = null
+            return 1
+        }
+        return 0
+    }
+
+    fun updatePaypwderror(ok: Boolean): Boolean {
+        return if (ok) {
+            if (this.paypwderror > 0) {
+                this.paypwderror = 0
+                this.paypwderrortime = null
+                true
+            } else {
+                false
+            }
+        } else {
+            if (this.paypwderror == 0) {
+                this.paypwderrortime = System.currentTimeMillis()
+            }
+            this.paypwderror += 1
+            true
+        }
+    }
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/domain/TApiClient.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/domain/TApiClient.kt
new file mode 100644
index 0000000..404e64a
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/domain/TApiClient.kt
@@ -0,0 +1,48 @@
+package com.supwisdom.oauth.domain
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+
+import javax.persistence.*
+import javax.validation.constraints.NotNull
+
+@Entity
+@Table(name = "TB_APICLIENT", indexes = [Index(name = "apiclient_idx", columnList = "appid, tenantid", unique = true)])
+class TApiClient {
+    @Id
+    @SequenceGenerator(name = "apiclient_id", sequenceName = "SEQ_APICLIENT", allocationSize = 1, initialValue = 10)
+    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "apiclient_id")
+    @Column(name = "id")
+    @NotNull
+    var id: Int? = null
+
+    @Column(name = "appid", length = 20)
+    @NotNull
+    var appid: String? = null
+
+    @Column(name = "secret", length = 64)
+    @NotNull
+    var secret: String? = null
+        set(secret) {
+            field = secret
+            this.bcryptSecret = BCryptPasswordEncoder().encode(secret)
+        }
+
+    @Column(name = "status", length = 10)
+    @NotNull
+    var status: String? = null
+
+    @Column(name = "roles", length = 300)
+    var roles: String? = null
+
+    @Column(name = "BCRYPT_SECRET", length = 64)
+    @NotNull
+    var bcryptSecret: String? = null
+        private set
+
+    @Column(name = "THIRDURL", length = 200)
+    var thirdurl: String? = null
+
+    @Column(name = "tenantid", length = 20)
+    @NotNull
+    var tenantId: String? = null
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt
new file mode 100644
index 0000000..16e27ba
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt
@@ -0,0 +1,140 @@
+package com.supwisdom.oauth
+
+import com.supwisdom.oauth.dao.ApiClientDao
+import com.supwisdom.oauth.util.Constants
+import com.supwisdom.oauth.util.JwtConfig
+import com.supwisdom.oauth.util.PasswordBCryptConfig
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.redis.connection.RedisConnectionFactory
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer
+import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer
+import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer
+import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer
+import org.springframework.security.oauth2.provider.ClientDetails
+import org.springframework.security.oauth2.provider.ClientDetailsService
+import org.springframework.security.oauth2.provider.OAuth2Authentication
+import org.springframework.security.oauth2.provider.client.BaseClientDetails
+import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore
+import org.springframework.web.bind.annotation.RequestHeader
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import java.security.Principal
+import java.util.*
+import java.security.SecureRandom
+
+@RestController
+class UserInforController {
+    @Autowired
+    lateinit var jwtConfig: JwtConfig
+    @Autowired
+    private lateinit var redisConnectionFactory: RedisConnectionFactory
+
+    @RequestMapping("/userinfor")
+    fun user(@RequestParam("access_token") access_token: String?,
+             @RequestHeader(Constants.HEADER_AUTHORIZATION) auth: String?): ResponseEntity<Any> {
+        if (access_token.isNullOrEmpty() && auth.isNullOrEmpty()) {
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+        }
+        var jwt: String
+        if(!auth.isNullOrEmpty()){
+            jwt = auth.substring(jwtConfig.tokenHeader.length)
+        }else{
+            jwt = access_token!!
+        }
+        val obj: OAuth2Authentication? = RedisTokenStore(redisConnectionFactory).readAuthentication(jwt) ?: return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+        val user = obj!!.userAuthentication.principal as UserDetails
+        if(user.username.isNullOrEmpty()){
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+        }
+        return ResponseEntity.status(HttpStatus.OK).body("""{"name":"${user.username}"}""")
+    }
+}
+
+class OAuthDetailService : ClientDetailsService {
+    @Autowired
+    private lateinit var apiClientDao: ApiClientDao
+    override fun loadClientByClientId(clientId: String?): ClientDetails {
+        val details = BaseClientDetails()
+        if (clientId.isNullOrEmpty()) {
+            return details
+        }
+        details.clientId = clientId
+        apiClientDao.findByAppid(clientId)?.let {
+            details.setAuthorizedGrantTypes(Arrays.asList("password","authorization_code","refresh_token"))
+            details.setScope(Arrays.asList("read"))
+            details.setResourceIds(Arrays.asList("oauth2-resource"))
+            val authorities = HashSet<GrantedAuthority>()
+            authorities.add(SimpleGrantedAuthority("ROLE_THIRD_ADMIN"))
+            details.authorities = authorities
+            details.setAutoApproveScopes(Arrays.asList("true"))
+            details.clientSecret = it.bcryptSecret
+            details.accessTokenValiditySeconds = 3600
+            details.refreshTokenValiditySeconds=43200
+            if(!it.thirdurl.isNullOrEmpty()){
+                val redir = HashSet<String>()
+                when {
+                    it.thirdurl!!.contains(",") -> redir.addAll(it.thirdurl!!.split(","))
+                    it.thirdurl!!.contains(";") -> redir.addAll(it.thirdurl!!.split(";"))
+                    else -> redir.add(it.thirdurl!!)
+                }
+                details.registeredRedirectUri = redir
+            }
+
+        }
+        return details
+    }
+}
+
+
+@Configuration
+class OAuth2Config {
+
+    @Configuration
+    @EnableAuthorizationServer
+
+    class AuthorizationServerConfigure : AuthorizationServerConfigurerAdapter() {
+
+        @Autowired
+        private lateinit var redisConnectionFactory: RedisConnectionFactory
+
+        @Autowired
+        private lateinit var authenticationManager: AuthenticationManager
+
+        @Autowired
+        lateinit var passwordBCryptConfig: PasswordBCryptConfig
+
+        override fun configure(security: AuthorizationServerSecurityConfigurer?) {
+            security?.allowFormAuthenticationForClients()
+            security?.passwordEncoder(pwdEncoder())
+        }
+        @Bean
+        fun pwdEncoder(): BCryptPasswordEncoder {
+            return if (passwordBCryptConfig.seed.isBlank()) {
+                BCryptPasswordEncoder()
+            } else {
+                BCryptPasswordEncoder(passwordBCryptConfig.length,
+                        SecureRandom(passwordBCryptConfig.seed.toByteArray()))
+            }
+        }
+
+        override fun configure(clients: ClientDetailsServiceConfigurer?) {
+            clients?.withClientDetails(OAuthDetailService())
+        }
+
+        override fun configure(endpoints: AuthorizationServerEndpointsConfigurer?) {
+            endpoints?.tokenStore(RedisTokenStore(redisConnectionFactory))
+                    ?.authenticationManager(authenticationManager)
+        }
+    }
+}
\ No newline at end of file
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt
new file mode 100644
index 0000000..f70bb02
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt
@@ -0,0 +1,167 @@
+package com.supwisdom.dlpay
+
+import com.supwisdom.oauth.domain.OAuthUser
+import com.supwisdom.oauth.service.OAuthUserService
+import com.supwisdom.oauth.util.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.BadCredentialsException
+import org.springframework.security.authentication.LockedException
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.http.SessionCreationPolicy
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.AuthenticationException
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
+import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher
+import org.springframework.stereotype.Component
+import java.io.IOException
+import java.security.SecureRandom
+import javax.servlet.ServletException
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import javax.sql.DataSource
+
+@Component("authLoginSuccessHandler")
+class AuthLoginSuccessHandler : SimpleUrlAuthenticationSuccessHandler() {
+    @Autowired
+    lateinit var userService: OAuthUserService
+
+    @Override
+    override fun onAuthenticationSuccess(request: HttpServletRequest, response: HttpServletResponse, authentication: Authentication) {
+        val temp = authentication.principal as OAuthUser
+        val user = userService.getByUid(temp.uid)
+        if (user != null) {
+            if (user.loginpwderror > 0) {
+                user.loginpwderror = 0
+                user.loginpwderrortime = null
+            }
+            user.lastlogin = DateUtil.getNow()
+            userService.saveUser(user)
+            super.onAuthenticationSuccess(request, response, authentication)
+        } else {
+            throw UserLoginFailException("登录错误")
+        }
+    }
+}
+@Component("authLoginFailHandler")
+class AuthLoginFailHandler : SimpleUrlAuthenticationFailureHandler() {
+    @Autowired
+    lateinit var userService: OAuthUserService
+
+    @Throws(IOException::class, ServletException::class)
+    override fun onAuthenticationFailure(request: HttpServletRequest,
+                                         response: HttpServletResponse, exception: AuthenticationException) {
+        val errmsg = when (exception) {
+            is BadCredentialsException -> "手机号或密码错误"
+            is LockedException -> "账户被锁定"
+            else -> exception.message!!
+        }
+        val temp = request.getParameter("username")
+        if(!temp.isNullOrEmpty()) {
+            userService.findByLoginid(temp)?.let {
+                if (it.loginpwderror == 0) {
+                    it.loginpwderror = 0
+                    it.loginpwderrortime = System.currentTimeMillis()
+                }
+                it.loginpwderror += 1
+                userService.saveUser(it)
+            }
+        }
+        setDefaultFailureUrl("/login")
+        super.onAuthenticationFailure(request, response, ValidateCodeException(errmsg))
+    }
+}
+
+
+
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+class WebSecurityConfig {
+
+    companion object {
+
+        @Configuration
+        class MvcWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
+            @Autowired
+            lateinit var dataSource: DataSource
+            @Autowired
+            lateinit var validateCodeSecurityConfig: ValidateCodeSecurityConfig
+            @Autowired
+            lateinit var authLoginFailHandler: AuthLoginFailHandler
+            @Autowired
+            lateinit var authLoginSuccessHandler: AuthLoginSuccessHandler
+            @Autowired
+            lateinit var passwordBCryptConfig: PasswordBCryptConfig
+            @Autowired
+            lateinit var userDetailsService: OAuthUserService
+
+            override fun configure(auth: AuthenticationManagerBuilder) {
+                auth.authenticationProvider(authenticationProvider())
+            }
+
+            @Bean
+            fun authenticationProvider(): DaoAuthenticationProvider {
+                return DaoAuthenticationProvider().apply {
+                    setUserDetailsService(userDetailsService)
+                    setPasswordEncoder(passwordEncoder())
+                }
+            }
+
+            @Bean
+            fun passwordEncoder(): BCryptPasswordEncoder {
+                return if (passwordBCryptConfig.seed.isBlank()) {
+                    BCryptPasswordEncoder()
+                } else {
+                    BCryptPasswordEncoder(passwordBCryptConfig.length,
+                            SecureRandom(passwordBCryptConfig.seed.toByteArray()))
+                }
+            }
+
+            @Bean
+            fun jdbcTokenImplement(): JdbcTokenRepositoryImpl {
+                return JdbcTokenRepositoryImpl().also {
+                    it.dataSource = dataSource
+                }
+            }
+
+            override fun configure(http: HttpSecurity) {
+                // 设置 Web MVC 应用权限
+                http.apply(validateCodeSecurityConfig)
+                        .and()
+                        .authorizeRequests()
+                        .antMatchers("/login", "/login/form", "/userinfor").permitAll()
+                        .antMatchers("/static/**").permitAll()
+                        .antMatchers("/code/image").permitAll()
+                        .antMatchers("/**").hasAnyRole("USER", "ADMIN")
+                        .anyRequest().authenticated()
+                        .and()
+                        .sessionManagement()
+                        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
+                        .and()
+                        .formLogin()
+                        .loginPage("/login")
+                        .loginProcessingUrl("/login/form")
+                        .failureHandler(authLoginFailHandler)
+                        .successHandler(authLoginSuccessHandler)
+                        .defaultSuccessUrl("/", false)
+                        .and()
+                        .logout()
+                        .logoutRequestMatcher(AntPathRequestMatcher("/logout"))
+                        .logoutSuccessUrl("/login")
+                        .deleteCookies("JSESSIONID")
+                        .invalidateHttpSession(true)
+                        .and().csrf().ignoringAntMatchers("oauth/**")
+
+            }
+        }
+    }
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/service/OAuthUserService.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/service/OAuthUserService.kt
new file mode 100644
index 0000000..5df80ef
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/service/OAuthUserService.kt
@@ -0,0 +1,12 @@
+package com.supwisdom.oauth.service
+
+import com.supwisdom.oauth.domain.OAuthUser
+import org.springframework.security.core.userdetails.UserDetailsService
+
+interface OAuthUserService:UserDetailsService{
+    fun getByUid(uid: String): OAuthUser?
+
+    fun findByLoginid(loginid: String): OAuthUser?
+
+    fun saveUser(oAuthUser: OAuthUser):OAuthUser
+}
\ No newline at end of file
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/service/impl/OAuthUserServiceImpl.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/service/impl/OAuthUserServiceImpl.kt
new file mode 100644
index 0000000..47364bd
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/service/impl/OAuthUserServiceImpl.kt
@@ -0,0 +1,60 @@
+package com.supwisdom.oauth.service.impl
+
+import com.supwisdom.oauth.dao.OAuthUserDao
+import com.supwisdom.oauth.domain.OAuthUser
+import com.supwisdom.oauth.service.OAuthUserService
+import com.supwisdom.oauth.util.UserLoginFailException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.authority.AuthorityUtils
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+import org.springframework.stereotype.Service
+
+@Service
+class OAuthUserServiceImpl :OAuthUserService {
+    companion object {
+        const val TIME_INTERVAL = 1000 * 6 * 30
+    }
+    @Autowired
+    lateinit var oAuthUserDao: OAuthUserDao
+
+    override fun loadUserByUsername(username: String?): UserDetails {
+        if (username.isNullOrEmpty()) {
+            throw UsernameNotFoundException("用户不存在")
+        }
+        val temp = oAuthUserDao.findByLoginid(username)
+        if (temp != null) {
+            if (temp.loginpwd.isEmpty()) {
+                throw UserLoginFailException("用户注册后未设置登录密码,请找回密码或重新注册")
+            }
+            if (temp.loginpwderror >= 5 && (System.currentTimeMillis() - temp.loginpwderrortime!!) < TIME_INTERVAL) {
+                throw UserLoginFailException("密码错误次数过多,请30分钟后再试")
+            } else if (temp.loginpwderror >= 5 && (System.currentTimeMillis() - temp.loginpwderrortime!!) > TIME_INTERVAL) {
+                //更新时间
+                temp.loginpwderror = 0
+                temp.loginpwderrortime = null
+                oAuthUserDao.save(temp)
+            }
+            val authorities: Collection<GrantedAuthority> = AuthorityUtils.createAuthorityList("ROLE_USER")
+            temp.auths = authorities
+        } else {
+            throw UsernameNotFoundException("用户不存在")
+        }
+        return temp
+    }
+
+    override fun getByUid(uid: String): OAuthUser? {
+        return oAuthUserDao.findById(uid).let {
+            if (it.isPresent) it.get() else null
+        }
+    }
+
+    override fun findByLoginid(loginid: String): OAuthUser? {
+        return  oAuthUserDao.findByLoginid(loginid)
+    }
+
+    override fun saveUser(oAuthUser: OAuthUser): OAuthUser {
+        return oAuthUserDao.save(oAuthUser)
+    }
+}
\ No newline at end of file
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/Constants.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/Constants.java
new file mode 100644
index 0000000..7cf989f
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/Constants.java
@@ -0,0 +1,15 @@
+package com.supwisdom.oauth.util;
+
+public class Constants {
+  //  HTTP HEADER define
+  public static final String HEADER_TETANTID = "X-TENANT-ID";
+
+  // define
+  public static final String JWT_CLAIM_TENANTID = "tenantId";
+  public static final String JWT_CLAIM_UID = "uid";
+  public static final String JWT_CLAIM_AUTHORITIES = "authorities";
+  // 根商户ID
+  public static final Integer ROOT_SHOP_FID = 1;
+
+  public static final String HEADER_AUTHORIZATION = "Authorization";
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/DateUtil.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/DateUtil.java
new file mode 100644
index 0000000..5b355c6
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/DateUtil.java
@@ -0,0 +1,361 @@
+package com.supwisdom.oauth.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateUtil {
+  private static final Logger logger = LoggerFactory.getLogger(DateUtil.class);
+  public static final String DATE_FMT = "yyyyMMdd";
+  public static final String TIME_FMT = "HHmmss";
+  public static final String DATETIME_FMT = "yyyyMMddHHmmss";
+
+  /**
+   * Description: 返回一个当前时间 @return String 格式:yyyyMMddHHmmss @exception Modify
+   * History:
+   */
+  public static String getNow() {
+    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+    return sdf.format(new Date());
+  }
+
+
+  /**
+   * Description: 根据类型返回一个当前时间 @param partten String @return String 格式:partten
+   */
+  public static String getNow(String partten) {
+    SimpleDateFormat sdf = new SimpleDateFormat(partten);
+    return sdf.format(new Date());
+  }
+  /*
+  *
+  * */
+  public static String getNowInterDay(int intervalday) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+      Date d = new Date();
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis(d.getTime());
+      calendar.add(Calendar.DATE, intervalday);
+      return sdf.format(calendar.getTime());
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  /**
+   * Description: 得到一个特殊的时间 @param startTime String 格式:yyyyMMddHHmmss @param
+   * interval int 秒 @return String 格式:partten @exception Modify History:
+   */
+  public static String getNewTime(String startTime, int interval) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+      Date d = sdf.parse(startTime);
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis(d.getTime());
+      calendar.add(Calendar.SECOND, interval);
+      return sdf.format(calendar.getTime());
+    } catch (ParseException e) {
+      return startTime;
+    }
+  }
+
+  /**
+   * Description: 得到一个特殊的时间 @param startTime String 格式:partten @param
+   * interval int 秒 @return String 格式:partten @exception Modify History:
+   */
+  public static String getNewTime(String startTime, int interval, String partten) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat(partten);
+      Date d = sdf.parse(startTime);
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis(d.getTime());
+      calendar.add(Calendar.SECOND, interval);
+      return sdf.format(calendar.getTime());
+    } catch (ParseException e) {
+      return startTime;
+    }
+  }
+
+  public static String getNewDay(String startDay, int intervalday) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+      Date d = sdf.parse(startDay);
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis(d.getTime());
+      calendar.add(Calendar.DATE, intervalday);
+      return sdf.format(calendar.getTime());
+    } catch (ParseException e) {
+      return startDay;
+    }
+  }
+
+  /**
+   * 得到两个日期相差的天数 格式 yyyyMMdd @return diffdays = secondDay - firstDay
+   */
+  public static long getIntervalDay(String firstDay, String secondDay) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+      Date f = sdf.parse(firstDay);
+      Date s = sdf.parse(secondDay);
+      long time = s.getTime() - f.getTime();
+      return time / (24 * 60 * 60 * 1000);
+    } catch (ParseException e) {
+      return 0;
+    }
+  }
+
+  /**
+   * Description: 比较两个时间字符串的前后关系 @param firstTime String 格式:yyyyMMddHHmmss
+   *
+   * @param secondTime String 格式: yyyyMMddHHmmss @return int |
+   *                   firstTime=second int=0 | firstTime>secondTime int>0 |
+   *                   firstTime<secondTime int<0 @exception Modify History:
+   */
+  public static int compareDatetime(String firstTime, String secondTime) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+      Date f = sdf.parse(firstTime);
+      Date s = sdf.parse(secondTime);
+      return f.compareTo(s);
+    } catch (ParseException e) {
+      return 0;
+    }
+  }
+
+  /**
+   * Description: 比较两个时间字符串的前后关系 @param firstTime String 格式:pattern
+   *
+   * @param secondTime String 格式: yyyyMMddHHmmss @return int |
+   *                   firstTime=second int=0 | firstTime>secondTime int>0 |
+   *                   firstTime<secondTime int<0 @exception Modify History:
+   */
+  public static int compareDatetime(String firstTime, String secondTime, String pattern) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+      Date f = sdf.parse(firstTime);
+      Date s = sdf.parse(secondTime);
+      return f.compareTo(s);
+    } catch (ParseException e) {
+      return 0;
+    }
+  }
+
+  /**
+   * Description: 比较两个时间字符串的时间差 @param firstTime String 格式:yyyyMMddHHmmss
+   *
+   * @param secondTime String 格式: yyyyMMddHHmmss @param second int 格式 @return
+   *                   int | firstTime+seconds=secondTime int=0 | firstTime+seconds>secondTime
+   *                   int>0 | firstTime+seconds<secondTime int<0 @exception Modify History:
+   */
+  public static int compareDatetime(String firstTime, String secondTime, int seconds) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+      Date f = sdf.parse(firstTime);
+      Date s = sdf.parse(secondTime);
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis(f.getTime());
+      calendar.add(Calendar.SECOND, seconds);
+      Date temp = calendar.getTime();
+      return temp.compareTo(s);
+    } catch (Exception e) {
+      return 0;
+    }
+  }
+
+  /**
+   * Description: 对time重新格式化
+   */
+  public static String reformatDatetime(String time, String fromPattern, String toPattern) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat(fromPattern);
+      Date d = sdf.parse(time);
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis(d.getTime());
+      sdf = new SimpleDateFormat(toPattern);
+      return sdf.format(calendar.getTime());
+    } catch (Exception e) {
+      e.printStackTrace();
+      return time;
+    }
+  }
+
+  /**
+   * 获得两个字符串日期之间的时间差(单位毫秒) 格式 yyyyMMddHHmmss
+   */
+  public static long getInterval(String startTime, String endTime) {
+    long duration = 0;
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+      duration = sdf.parse(endTime).getTime() - sdf.parse(startTime).getTime();
+    } catch (ParseException e) {
+      logger.error("Hi guys,there is an error when you try to parse the date string");
+    }
+    return duration;
+  }
+
+  /**
+   * 获得两个字符串日期之间的时间差(单位毫秒)
+   */
+  public static long getIntervalTime(String startTime, String endTime, String pattern) {
+    long duration = 0;
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+      duration = sdf.parse(endTime).getTime() - sdf.parse(startTime).getTime();
+    } catch (ParseException e) {
+      logger.error("Hi guys,there is an error when you try to parse the date string");
+    }
+    return duration;
+  }
+
+  /**
+   * 转换成日期格式
+   * 短格式:20140401 -> 2014-04-01
+   * 中格式:201404011200 -> 2014-04-01 12:00
+   * 长格式:20140401123025 -> 2014-04-01 12:30:25
+   **/
+  public static String parseToDateFormat(String str) {
+    switch (str.length()) {
+      case 8:
+        str = str.substring(0, 4) + "-" + str.substring(4, 6) + "-" + str.substring(6, 8);
+        break;
+      case 12:
+        str = str.substring(0, 4) + "-" + str.substring(4, 6) + "-" + str.substring(6, 8) + " " + str.substring(8, 10) + ":" + str.substring(10, 12);
+        break;
+      case 14:
+        str = str.substring(0, 4) + "-" + str.substring(4, 6) + "-" + str.substring(6, 8) + " " + str.substring(8, 10) + ":" + str.substring(10, 12) + ":" + str.substring(12, 14);
+        break;
+      default:
+        break;
+    }
+    return str;
+  }
+
+  /**
+   * 解日期格式
+   * 短格式:2014-04-01 -> 20140401
+   * 中格式:2014-04-01 12:00 -> 201404011200
+   * 长格式:2014-04-01 12:30:25 -> 20140401123025
+   **/
+  public static String unParseToDateFormat(String str) {
+    return str.replaceAll("-", "").replaceAll(" ", "").replaceAll(":", "");
+  }
+
+  /**
+   * 检验时间格式
+   */
+  public static boolean checkDatetimeValid(String datetime, String pattern) {
+    if (null == datetime) return false;
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+      Date d = sdf.parse(datetime);
+      return datetime.trim().equals(sdf.format(d));
+    } catch (Exception e) {
+    }
+    return false;
+  }
+
+  /**
+   * 获取指定日期是星期几 格式 yyyyMMdd
+   * MON|TUE|WED|THU|FRI|SAT|SUN
+   * 1		2		3		4		5		6		7
+   */
+  public static int getWeekday(String datestr) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+      Calendar calendar = Calendar.getInstance();
+      boolean isFirstSunday = (calendar.getFirstDayOfWeek() == Calendar.SUNDAY); //一周第一天是否为星期天
+      Date d = sdf.parse(datestr);
+      calendar.setTimeInMillis(d.getTime());
+      int weekDay = calendar.get(calendar.DAY_OF_WEEK);
+      if (isFirstSunday) {
+        weekDay = weekDay - 1;
+        if (weekDay == 0) {
+          weekDay = 7;
+        }
+      }
+      return weekDay;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  /**
+   * 获取指定日期
+   */
+  public static Date getSpecifyDate(String datestr, String pattern) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+      Date result = sdf.parse(datestr);
+      return result;
+    } catch (Exception e) {
+      return new Date();
+    }
+  }
+
+  public static Integer getLastDayOfMonth(Integer year, Integer month) {
+    Calendar cal = Calendar.getInstance();
+    cal.set(Calendar.YEAR, year);
+    cal.set(Calendar.MONTH, month - 1);
+    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DATE));
+    String str = new SimpleDateFormat("yyyyMMdd ").format(cal.getTime()).toString();
+    Integer result = Integer.parseInt(str.substring(0, 4) + str.substring(4, 6) + str.substring(6, 8));
+    return result;
+  }
+
+  private static Date set(Date date, int calendarField, int amount) {
+    Calendar c = Calendar.getInstance();
+    c.setLenient(false);
+    c.setTime(date);
+    c.add(calendarField, amount);
+    return c.getTime();
+  }
+
+
+  public static Date setMinutes(Date date, int amount) {
+    return set(date, Calendar.MINUTE, amount);
+  }
+
+
+  public static long getNowSecond() {
+    Calendar calendar = Calendar.getInstance();
+    return calendar.getTimeInMillis() / 1000;
+  }
+
+
+  public static String getUTCTime(Long timeInMillisSecond) {
+    Calendar time = Calendar.getInstance();
+    SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
+    fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
+    time.setTimeInMillis(timeInMillisSecond);
+    return fmt.format(time.getTime());
+  }
+
+  public static String getUTCTime() {
+    return getUTCTime(System.currentTimeMillis());
+  }
+
+  public static int compareDay(Timestamp d1, Timestamp d2) {
+    Calendar cd1 = Calendar.getInstance();
+    cd1.setTimeInMillis(d1.getTime());
+    Calendar cd2 = Calendar.getInstance();
+    cd2.setTimeInMillis(d2.getTime());
+
+    if (cd1.get(Calendar.YEAR) != cd2.get(Calendar.YEAR)) {
+      return cd1.compareTo(cd2);
+    }
+
+    return Integer.compare(cd1.get(Calendar.DAY_OF_YEAR), cd2.get(Calendar.DAY_OF_YEAR));
+  }
+
+  public static Boolean sameDay(Timestamp d1, Timestamp d2) {
+    return (compareDay(d1, d2) == 0);
+  }
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtConfig.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtConfig.java
new file mode 100644
index 0000000..3bf4921
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtConfig.java
@@ -0,0 +1,36 @@
+package com.supwisdom.oauth.util;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JwtConfig {
+  @Value("${jwt.secret}")
+  private String secret;
+  @Value("${jwt.expiration:3600}")
+  private Long expiration = 3600L;
+  @Value("${jwt.header:Authorization}")
+  private String header = "Authorization";
+  @Value("${jwt.token_header:Bearer }")
+  private String tokenHeader = "Bearer";
+
+  public String getSecret() {
+    return secret;
+  }
+
+  public Long getExpiration() {
+    return expiration;
+  }
+
+  public String getHeader() {
+    return header;
+  }
+
+  public String getTokenHeader() {
+    return tokenHeader;
+  }
+
+  public void setExpiration(Long expiration) {
+    this.expiration = expiration;
+  }
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtToken.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtToken.java
new file mode 100644
index 0000000..8d17542
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtToken.java
@@ -0,0 +1,39 @@
+package com.supwisdom.oauth.util;
+
+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/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtTokenUtil.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtTokenUtil.java
new file mode 100644
index 0000000..9304dcd
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/JwtTokenUtil.java
@@ -0,0 +1,94 @@
+package com.supwisdom.oauth.util;
+
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jwk.JsonWebKey;
+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;
+import org.jose4j.lang.JoseException;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JwtTokenUtil {
+  private JwtConfig jwtConfig;
+
+  public JwtTokenUtil(JwtConfig config) {
+    this.jwtConfig = config;
+  }
+
+  public String getHeader() {
+    return jwtConfig.getHeader();
+  }
+
+  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) {
+      claims.setAudience(params.get("audience").toString());
+    }
+    claims.setExpirationTimeMinutesInTheFuture(jwtConfig.getExpiration() / 60); // time when the token will expire (10 minutes from now)
+    claims.setGeneratedJwtId();
+    claims.setIssuedAtToNow();  // when the token was issued/created (now)
+    claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
+    if (params.get("subject") != null) {
+      claims.setSubject(params.get("subject").toString()); // the subject/principal is whom the token is about
+    }
+    if (params.get(Constants.JWT_CLAIM_AUTHORITIES) != null) {
+      claims.setClaim(Constants.JWT_CLAIM_AUTHORITIES, params.get(Constants.JWT_CLAIM_AUTHORITIES));
+    }
+    if (params.get(Constants.JWT_CLAIM_UID) != null) {
+      claims.setClaim(Constants.JWT_CLAIM_UID, params.get(Constants.JWT_CLAIM_UID));
+    }
+    if (params.get(Constants.JWT_CLAIM_TENANTID) != null) {
+      claims.setClaim(Constants.JWT_CLAIM_TENANTID, params.get(Constants.JWT_CLAIM_TENANTID));
+    }
+    /*
+    claims.setClaim("email", "mail@example.com"); // additional claims/attributes about the subject can be added
+    List<String> groups = Arrays.asList("group-one", "other-group", "group-three");
+    claims.setStringListClaim("groups", groups); // multi-valued claims work too and will end up as a JSON array
+     */
+
+    Map<String, Object> keySpec = new HashMap<>();
+    keySpec.put("kty", "oct");
+    keySpec.put("k", jwtConfig.getSecret());
+    JsonWebKey key = JsonWebKey.Factory.newJwk(keySpec);
+    JsonWebSignature jws = new JsonWebSignature();
+    jws.setPayload(claims.toJson());
+    jws.setKey(key.getKey());
+    jws.setKeyIdHeaderValue(key.getKeyId());
+    jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
+    return new JwtToken(claims.getJwtId(), jws.getCompactSerialization(), claims.getExpirationTime());
+  }
+
+  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, Object> verifyToken(String token) throws JoseException, InvalidJwtException {
+    Map<String, Object> keySpec = new HashMap<>();
+    keySpec.put("kty", "oct");
+    keySpec.put("k", jwtConfig.getSecret());
+    JsonWebKey key = JsonWebKey.Factory.newJwk(keySpec);
+    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
+        .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))
+        .build(); // create the JwtConsumer instance
+
+    //  Validate the JWT and process it to the Claims
+    JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
+    return jwtClaims.getClaimsMap();
+  }
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/PasswordBCryptConfig.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/PasswordBCryptConfig.java
new file mode 100644
index 0000000..55d2c03
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/PasswordBCryptConfig.java
@@ -0,0 +1,28 @@
+package com.supwisdom.oauth.util;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class PasswordBCryptConfig {
+  @Value("${auth.password.bcrypt.length:10}")
+  private Integer length;
+  @Value("${auth.password.bcrypt.seed}")
+  private String seed = "";
+
+  public Integer getLength() {
+    return length;
+  }
+
+  public void setLength(Integer length) {
+    this.length = length;
+  }
+
+  public String getSeed() {
+    return seed;
+  }
+
+  public void setSeed(String seed) {
+    this.seed = seed;
+  }
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/UserLoginFailException.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/util/UserLoginFailException.kt
new file mode 100644
index 0000000..ca7317a
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/UserLoginFailException.kt
@@ -0,0 +1,7 @@
+package com.supwisdom.oauth.util
+
+import org.springframework.security.core.AuthenticationException
+
+class UserLoginFailException(msg: String) : AuthenticationException(msg) {
+    private val serialVersionUID = 1170189980006964105L
+}
\ No newline at end of file
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/ValidateCodeException.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/ValidateCodeException.java
new file mode 100755
index 0000000..c719096
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/ValidateCodeException.java
@@ -0,0 +1,23 @@
+/**
+ * 
+ */
+package com.supwisdom.oauth.util;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * @author lenovo
+ *
+ */
+public class ValidateCodeException extends AuthenticationException {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1170189980006964105L;
+
+	
+	public ValidateCodeException(String msg) {
+		super(msg);
+	}
+}
diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/util/ValidateCodeSecurityConfig.java b/oauth/src/main/kotlin/com/supwisdom/oauth/util/ValidateCodeSecurityConfig.java
new file mode 100644
index 0000000..9f0118c
--- /dev/null
+++ b/oauth/src/main/kotlin/com/supwisdom/oauth/util/ValidateCodeSecurityConfig.java
@@ -0,0 +1,23 @@
+package com.supwisdom.oauth.util;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.Filter;
+
+@Component("validateCodeSecurityConfig")
+public class ValidateCodeSecurityConfig  extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity> {
+
+	@Autowired
+	private Filter validateCodeFilter;
+
+	@Override
+	public void configure(HttpSecurity http) throws Exception {
+		http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
+	}
+	
+}
\ No newline at end of file
diff --git a/oauth/src/main/resources/templates/login.html b/oauth/src/main/resources/templates/login.html
new file mode 100644
index 0000000..ac69dd1
--- /dev/null
+++ b/oauth/src/main/resources/templates/login.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <title>统一身份认证</title>
+    <meta name="_csrf" th:content="${_csrf.token}"/>
+    <!-- default header name is X-CSRF-TOKEN -->
+    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
+</head>
+<body>
+<div class="login-wrapper">
+    <div class=" login-body">
+        <div class="layui-card">
+            <div class="layui-card-header">
+                <i class="layui-icon layui-icon-engine"></i>&nbsp;&nbsp;统一身份认证
+            </div>
+            <div class="layui-card-body layui-form layui-form-pane">
+                <p style="color:red;padding: 0 0 10px 0;" th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']!=null and session['SPRING_SECURITY_LAST_EXCEPTION'].message!=null}" th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}"></p>
+                <form th:action="@{/login/form}" method="post">
+                    <div class="layui-form-item">
+                        <label class="layui-form-label"><i class="layui-icon layui-icon-username"></i></label>
+                        <div class="layui-input-block">
+                            <input name="username" type="text" lay-verify="required" placeholder="账号"
+                                   class="layui-input">
+                            <input type="hidden"
+                                   th:name="${_csrf.parameterName}"
+                                   th:value="${_csrf.token}"/>
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label"><i class="layui-icon layui-icon-password"></i></label>
+                        <div class="layui-input-block">
+                            <input name="password" type="password" lay-verify="required" placeholder="密码"
+                                   class="layui-input">
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label"><i class="layui-icon layui-icon-vercode"></i></label>
+                        <div class="layui-input-block">
+                            <div class="layui-row inline-block">
+                                <div class="layui-col-xs7">
+                                    <input name="verifyCodeActual" type="text" placeholder="验证码"
+                                           class="layui-input">
+                                </div>
+                                <div class="layui-col-xs5" style="padding-left: 10px;">
+                                    <img width="100px" height="35px" class="login-captcha" th:src="@{/code/image}">
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <!--<a href="javascript:;" class="layui-link">帐号注册</a>-->
+                        <a href="javascript:;" class="layui-link pull-right">忘记密码?</a>
+                    </div>
+                    <div class="layui-form-item">
+                        <button type="submit" lay-filter="login-submit" class="layui-btn layui-btn-fluid" lay-submit>登 录</button>
+                    </div>
+                </form>
+                <!--<div class="layui-form-item login-other">-->
+                <!--<label>第三方登录</label>-->
+                <!--<a href="javascript:;"><i class="layui-icon layui-icon-login-qq"></i></a>-->
+                <!--<a href="javascript:;"><i class="layui-icon layui-icon-login-wechat"></i></a>-->
+                <!--<a href="javascript:;"><i class="layui-icon layui-icon-login-weibo"></i></a>-->
+                <!--</div>-->
+            </div>
+        </div>
+    </div>
+
+    <div class="login-footer">
+        <p>© 2019 <a href="javascript:;" target="_blank">上海树维信息科技有限公司 版权所有</a></p>
+        <!--<p>-->
+        <!--<span><a href="javascript:;" target="_blank">前往github</a></span>-->
+        <!--<span><a href="https://gitee.com/andyzy/zy-admin.git" target="_blank">前往gitee</a></span>-->
+        <!--<span><a href="javascript:;" target="_blank">文档说明</a></span>-->
+        <!--</p>-->
+    </div>
+</div>
+</body>
+</html>
\ No newline at end of file
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 0bf11d8..a58a7f5 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
@@ -11,4 +11,6 @@
   public static final String JWT_CLAIM_AUTHORITIES = "authorities";
   // 根商户ID
   public static final Integer ROOT_SHOP_FID = 1;
+
+  public static final String HEADER_AUTHORIZATION = "Authorization";
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
index 8ca8d92..db0f82d 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
@@ -21,12 +21,18 @@
 import com.supwisdom.dlpay.framework.util.*
 import com.supwisdom.dlpay.system.service.FunctionService
 import mu.KotlinLogging
+import org.jose4j.jwt.ReservedClaimNames
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.data.redis.connection.RedisConnectionFactory
 import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
+import org.springframework.security.core.Authentication
 import org.springframework.security.core.annotation.AuthenticationPrincipal
 import org.springframework.security.core.context.SecurityContextHolder
 import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper
+import org.springframework.security.oauth2.provider.OAuth2Authentication
+import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore
 import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
 import org.springframework.social.connect.web.HttpSessionSessionStrategy
 import org.springframework.stereotype.Controller
@@ -204,11 +210,29 @@
 
 @RestController
 class UserInforController {
+    @Autowired
+    lateinit var jwtConfig: JwtConfig
+    @Autowired
+    private lateinit var redisConnectionFactory: RedisConnectionFactory
 
-    @RequestMapping("/api/userinfor")
-    fun user(user: Principal): Principal {
-        System.out.println(user)
-        return user
+    @RequestMapping("/userinfor")
+    fun user(@RequestParam("access_token") access_token: String?,
+             @RequestHeader(Constants.HEADER_AUTHORIZATION) auth: String?): ResponseEntity<Any> {
+        if (access_token.isNullOrEmpty() && auth.isNullOrEmpty()) {
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+        }
+        var jwt: String
+        if(!auth.isNullOrEmpty()){
+            jwt = auth.substring(jwtConfig.tokenHeader.length)
+        }else{
+            jwt = access_token!!
+        }
+        val obj: OAuth2Authentication? = RedisTokenStore(redisConnectionFactory).readAuthentication(jwt) ?: return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+        val user = obj!!.userAuthentication.principal as UserDetails
+        if(user.username.isNullOrEmpty()){
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
+        }
+        return ResponseEntity.status(HttpStatus.OK).body("""{"name":"${user.username}"}""")
     }
 }
 
@@ -220,6 +244,7 @@
     @Autowired
     lateinit var commonService: CommonService
 
+
     private val logger = KotlinLogging.logger {}
 
     @GetMapping("/login")
@@ -260,6 +285,7 @@
         return "index"
     }
 
+
 }
 
 @Controller
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/oauth.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/oauth.kt
index a249193..d007c7e 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/oauth.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/oauth.kt
@@ -1,13 +1,16 @@
 package com.supwisdom.dlpay
 
+import com.supwisdom.dlpay.framework.core.PasswordBCryptConfig
 import com.supwisdom.dlpay.system.service.ParamService
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.data.redis.connection.RedisConnectionFactory
 import org.springframework.security.authentication.AuthenticationManager
 import org.springframework.security.core.Authentication
 import org.springframework.security.core.GrantedAuthority
 import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
 import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer
 import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter
 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer
@@ -23,8 +26,7 @@
 import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore
 import java.util.*
 import org.springframework.security.oauth2.provider.token.TokenStore
-
-
+import java.security.SecureRandom
 
 
 class OAuthDetailService : ClientDetailsService {
@@ -47,9 +49,16 @@
             details.clientSecret = it.bcryptSecret
             details.accessTokenValiditySeconds = 3600
             details.refreshTokenValiditySeconds=43200
-            val redir = HashSet<String>()
-            redir.add("http://localhost:8091/wt/sso/login")
-            details.registeredRedirectUri = redir
+            if(!it.thirdurl.isNullOrEmpty()){
+                val redir = HashSet<String>()
+                when {
+                    it.thirdurl.contains(",") -> redir.addAll(it.thirdurl.split(","))
+                    it.thirdurl.contains(";") -> redir.addAll(it.thirdurl.split(";"))
+                    else -> redir.add(it.thirdurl)
+                }
+                details.registeredRedirectUri = redir
+            }
+
         }
         return details
     }
@@ -70,9 +79,21 @@
         @Autowired
         private lateinit var authenticationManager: AuthenticationManager
 
+        @Autowired
+        lateinit var passwordBCryptConfig: PasswordBCryptConfig
 
         override fun configure(security: AuthorizationServerSecurityConfigurer?) {
             security?.allowFormAuthenticationForClients()
+            security?.passwordEncoder(pwdEncoder())
+        }
+        @Bean
+        fun pwdEncoder(): BCryptPasswordEncoder {
+            return if (passwordBCryptConfig.seed.isBlank()) {
+                BCryptPasswordEncoder()
+            } else {
+                BCryptPasswordEncoder(passwordBCryptConfig.length,
+                        SecureRandom(passwordBCryptConfig.seed.toByteArray()))
+            }
         }
 
         override fun configure(clients: ClientDetailsServiceConfigurer?) {
diff --git a/settings.gradle b/settings.gradle
index 42be481..35b2274 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,3 @@
 rootProject.name = 'payapi'
-include 'payapi', 'payapi-sdk', 'payapi-common', 'ynrcc-agent'
+include 'payapi', 'payapi-sdk', 'payapi-common', 'ynrcc-agent','oauth'
+