Browse Source

add xms-auth service

liyan 4 years ago
parent
commit
b1f70ab39a
100 changed files with 3635 additions and 0 deletions
  1. 32 0
      xms-auth/.gitignore
  2. 8 0
      xms-auth/README.md
  3. 49 0
      xms-auth/apps/auth-agent/build.gradle.kts
  4. BIN
      xms-auth/apps/auth-agent/libs/xms-fingerprint-mock-1.0.0.jar
  5. 13 0
      xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AgentConfiguration.kt
  6. 11 0
      xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AgentConfigure.kt
  7. 21 0
      xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AgentController.kt
  8. 11 0
      xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AuthAgentApplication.kt
  9. 21 0
      xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/domain/CaptureData.kt
  10. 19 0
      xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/domain/CaptureForm.kt
  11. 36 0
      xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/service/AgentService.kt
  12. 15 0
      xms-auth/apps/auth-agent/src/main/resources/application.yml
  13. 1 0
      xms-auth/apps/auth-in-one/.gitignore
  14. 105 0
      xms-auth/apps/auth-in-one/build.gradle.kts
  15. 10 0
      xms-auth/apps/auth-in-one/script/start.sh
  16. 11 0
      xms-auth/apps/auth-in-one/script/stop.sh
  17. 59 0
      xms-auth/apps/auth-in-one/src/main/kotlin/jit/xms/auth/allinone/AuthInOneApplication.kt
  18. 181 0
      xms-auth/apps/auth-in-one/src/main/resources/api/api.html
  19. 167 0
      xms-auth/apps/auth-in-one/src/main/resources/api/api.raml
  20. 35 0
      xms-auth/apps/auth-in-one/src/main/resources/application-auth.yml
  21. 15 0
      xms-auth/apps/auth-in-one/src/main/resources/application-local.yml
  22. 74 0
      xms-auth/apps/auth-in-one/src/main/resources/application-routes.yml
  23. 23 0
      xms-auth/apps/auth-in-one/src/main/resources/application.yml
  24. 3 0
      xms-auth/apps/build.gradle.kts
  25. 94 0
      xms-auth/build.gradle.kts
  26. 14 0
      xms-auth/gradle.properties
  27. BIN
      xms-auth/gradle/wrapper/gradle-wrapper.jar
  28. 5 0
      xms-auth/gradle/wrapper/gradle-wrapper.properties
  29. 183 0
      xms-auth/gradlew
  30. 103 0
      xms-auth/gradlew.bat
  31. BIN
      xms-auth/libs/Dm7JdbcDriver18.jar
  32. BIN
      xms-auth/libs/DmDialect-for-hibernate5.3.jar
  33. 6 0
      xms-auth/services/auth-adaptor/README.md
  34. 4 0
      xms-auth/services/auth-adaptor/build.gradle.kts
  35. 13 0
      xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/AuthAdaptorApplication.kt
  36. 17 0
      xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/AuthAdaptorConfiguration.kt
  37. 18 0
      xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/AuthAdaptorController.kt
  38. 13 0
      xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/domain/AuthForm.kt
  39. 53 0
      xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/service/AuthAdaptorService.kt
  40. 15 0
      xms-auth/services/auth-adaptor/src/main/resources/application-db.yml
  41. 14 0
      xms-auth/services/auth-adaptor/src/main/resources/application.yml
  42. 124 0
      xms-auth/services/build.gradle.kts
  43. 3 0
      xms-auth/services/cert-down/README.md
  44. 3 0
      xms-auth/services/cert-down/sh/start-9065.sh
  45. 2 0
      xms-auth/services/cert-down/sh/start.bat
  46. 3 0
      xms-auth/services/cert-down/sh/stop-9065.sh
  47. 40 0
      xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/CertDownApplication.kt
  48. 31 0
      xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/CertDownConfiguration.kt
  49. 43 0
      xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/CertDownController.kt
  50. 72 0
      xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/domain/CertData.kt
  51. 8 0
      xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/domain/CertForm.kt
  52. 12 0
      xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/domain/UserData.kt
  53. 139 0
      xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/service/CertDownService.kt
  54. 15 0
      xms-auth/services/cert-down/src/main/resources/application-db.yml
  55. 48 0
      xms-auth/services/cert-down/src/main/resources/application-routes.yml
  56. 14 0
      xms-auth/services/cert-down/src/main/resources/application.yml
  57. 3 0
      xms-auth/services/multi-factor/README.md
  58. 3 0
      xms-auth/services/multi-factor/sh/start-9060.sh
  59. 2 0
      xms-auth/services/multi-factor/sh/start.bat
  60. 3 0
      xms-auth/services/multi-factor/sh/stop-9060.sh
  61. 41 0
      xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/AuthConfiguration.kt
  62. 22 0
      xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/AuthConfigure.kt
  63. 51 0
      xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/AuthServiceController.kt
  64. 40 0
      xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/MultiFactorAuthApplication.kt
  65. 19 0
      xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/domain/AuthForm.kt
  66. 13 0
      xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/domain/LoginForm.kt
  67. 289 0
      xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/service/AuthService.kt
  68. 25 0
      xms-auth/services/multi-factor/src/main/resources/application-auth.yml
  69. 15 0
      xms-auth/services/multi-factor/src/main/resources/application-db.yml
  70. 48 0
      xms-auth/services/multi-factor/src/main/resources/application-routes.yml
  71. 13 0
      xms-auth/services/multi-factor/src/main/resources/application.yml
  72. 13 0
      xms-auth/settings.gradle.kts
  73. 3 0
      xms-auth/sh/README.md
  74. 24 0
      xms-auth/sh/start-port.sh
  75. 18 0
      xms-auth/sh/stop-port.sh
  76. 40 0
      xms-auth/shared/agent-ware-api/build.gradle.kts
  77. 48 0
      xms-auth/shared/agent-ware-api/src/main/java/jit/xms/auth/agent/spi/AgentMultiFactorProvider.java
  78. 7 0
      xms-auth/shared/agent-ware-api/src/main/java/jit/xms/auth/agent/spi/AgentWareException.java
  79. 62 0
      xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/config/AgentMultiFactorConfigure.kt
  80. 77 0
      xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/config/MultiFactorProviderProperties.kt
  81. 27 0
      xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/web/AgentControllerAdvice.kt
  82. 23 0
      xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/web/AgentWebAdviceConfiguration.kt
  83. 27 0
      xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/web/reactive/AgentControllerAdvice.kt
  84. 40 0
      xms-auth/shared/multi-factor-api/build.gradle.kts
  85. 7 0
      xms-auth/shared/multi-factor-api/src/main/java/jit/xms/auth/api/MultiFactorException.java
  86. 42 0
      xms-auth/shared/multi-factor-api/src/main/java/jit/xms/auth/api/spi/MultiFactorProvider.java
  87. 6 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AppInfo.kt
  88. 6 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AuthInfo.kt
  89. 6 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AuthTicket.kt
  90. 6 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AuthToken.kt
  91. 34 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/UserInfo.kt
  92. 86 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/config/MultiFactorConfigure.kt
  93. 85 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/config/ProviderProperties.kt
  94. 27 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/web/MultiFactorControllerAdvice.kt
  95. 23 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/web/MultiFactorWebAdviceConfiguration.kt
  96. 27 0
      xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/web/reactive/MultiFactorControllerAdvice.kt
  97. 41 0
      xms-auth/shared/util/build.gradle.kts
  98. 48 0
      xms-auth/shared/util/src/main/kotlin/jit/xms/auth/util/support/config/AuthGatewayConfigure.kt
  99. 166 0
      xms-auth/shared/util/src/main/kotlin/jit/xms/auth/util/support/proxy/AuthGatewayService.kt
  100. 0 0
      xms-auth/shared/util/src/main/kotlin/jit/xms/auth/util/support/proxy/SocketClient.kt

+ 32 - 0
xms-auth/.gitignore

@@ -0,0 +1,32 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/

+ 8 - 0
xms-auth/README.md

@@ -0,0 +1,8 @@
+# TODO LIST
+1. 网关增加JwtParse和JwtVerify过滤器
+2. 通过配置文件限定认证服务是否支持一对多比对模式 √
+3. 增加多生物特征认证支持 √
+4. 增加UKey凭证类型支持,通过005验证签名信息
+5. 增加对生物特征数据的脱密处理
+6. 增加生物特征下载处理,支持生物特征数据使用临时公钥重新加密
+7. 增加认证客户端代理架构和接口设计

+ 49 - 0
xms-auth/apps/auth-agent/build.gradle.kts

@@ -0,0 +1,49 @@
+val patchVersion: String by project
+
+group = "jit.xms"
+version = "${rootProject.version}.$patchVersion"
+
+plugins {
+    id("java")
+    id("io.spring.dependency-management")
+    id("org.springframework.boot")
+    kotlin("jvm")
+    kotlin("plugin.spring")
+}
+
+dependencies {
+    implementation(kotlin("reflect"))
+    implementation(kotlin("stdlib-jdk8"))
+    implementation(fileTree("$projectDir/libs") { include("*.jar") })
+//    implementation(fileTree("$rootDir/libs") { include("gaf-core-shared-*.jar") })
+    implementation("cc-lotus.gaf3:gaf-core-shared:${property("gafVersion")}")
+    implementation(project(path = ":shared:agent-ware-api"))
+    implementation("org.springframework.boot:spring-boot-starter-webflux")
+    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
+    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
+    implementation("javax.validation:validation-api")
+    runtimeOnly("org.hibernate.validator:hibernate-validator")
+}
+
+
+tasks {
+    processResources {
+        filesMatching("application.yml") {
+            // expand("db.user" to dbUser)
+            expand(project.properties)
+        }
+    }
+}
+
+task<Copy>("dist") {
+    into("$rootDir/dist/${project.name}")
+    from(tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar>())
+    from("$projectDir/sh")
+    from("$buildDir/resources/main") {
+        include("*.yml")
+        into("config")
+    }
+    from("$rootDir/sh") {
+        into("sbin")
+    }
+}

BIN
xms-auth/apps/auth-agent/libs/xms-fingerprint-mock-1.0.0.jar


+ 13 - 0
xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AgentConfiguration.kt

@@ -0,0 +1,13 @@
+package jit.xms.auth.agent
+
+import gaf3.core.cloud.GafCloudConfiguration
+import jit.xms.agent.support.config.AgentMultiFactorConfigure
+import jit.xms.agent.support.web.AgentWebAdviceConfiguration
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+
+@Configuration
+@EnableConfigurationProperties(AgentConfigure::class, AgentMultiFactorConfigure::class)
+@Import(GafCloudConfiguration::class, AgentWebAdviceConfiguration::class)
+class AgentConfiguration

+ 11 - 0
xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AgentConfigure.kt

@@ -0,0 +1,11 @@
+package jit.xms.auth.agent
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.validation.annotation.Validated
+
+@ConfigurationProperties(prefix = "xms.agent")
+@Validated
+class AgentConfigure {
+    // TODO: some configure
+    var foo: String? = "bar"
+}

+ 21 - 0
xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AgentController.kt

@@ -0,0 +1,21 @@
+package jit.xms.auth.agent
+
+import jit.xms.auth.agent.domain.CaptureData
+import jit.xms.auth.agent.domain.CaptureForm
+import jit.xms.auth.agent.service.AgentService
+import org.springframework.web.bind.annotation.*
+import javax.validation.Valid
+
+@RestController
+@RequestMapping(path = ["/xms/agent"])
+@CrossOrigin(origins = ["*"], maxAge = 3600)
+class AgentController(val service: AgentService) {
+
+    /**
+     * 采集生物特征
+     */
+    @PostMapping(path = ["/capture"])
+    fun ticket(@RequestBody @Valid form: CaptureForm): CaptureData {
+        return service.capture(form)
+    }
+}

+ 11 - 0
xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/AuthAgentApplication.kt

@@ -0,0 +1,11 @@
+package jit.xms.auth.agent
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+
+@SpringBootApplication
+class AuthAgentApplication
+
+fun main(args: Array<String>) {
+    runApplication<AuthAgentApplication>(*args)
+}

+ 21 - 0
xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/domain/CaptureData.kt

@@ -0,0 +1,21 @@
+package jit.xms.auth.agent.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import javax.validation.constraints.NotEmpty
+import javax.validation.constraints.NotNull
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+open class CaptureData {
+    /**
+     * 凭证类型
+     */
+    var type: String? = null
+    /**
+     * 凭证数据
+     */
+    var data: String? = null
+    /**
+     * 图像数据,Base64编码
+     */
+    var image: String? = null
+}

+ 19 - 0
xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/domain/CaptureForm.kt

@@ -0,0 +1,19 @@
+package jit.xms.auth.agent.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import javax.validation.constraints.NotEmpty
+import javax.validation.constraints.NotNull
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+open class CaptureForm {
+    /**
+     * 凭证类型
+     */
+    @NotEmpty
+    var type: String? = null
+
+    /**
+     * 凭证名称
+     */
+    var name: String? = null
+}

+ 36 - 0
xms-auth/apps/auth-agent/src/main/kotlin/jit/xms/auth/agent/service/AgentService.kt

@@ -0,0 +1,36 @@
+package jit.xms.auth.agent.service
+
+import gaf3.core.exception.BusinessError
+import jit.xms.auth.agent.domain.CaptureData
+import jit.xms.auth.agent.domain.CaptureForm
+import jit.xms.agent.support.config.AgentMultiFactorConfigure
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+import org.springframework.validation.annotation.Validated
+import javax.validation.Valid
+
+
+@Service
+@Validated
+class AgentService(@Autowired val provConfig: AgentMultiFactorConfigure) {
+
+    fun capture(@Valid form: CaptureForm): CaptureData {
+        // 采集生物特征
+        val prov = provConfig.getProvider(form.type!!)
+        val buf = StringBuffer()
+        val result = prov.capture(buf, null) ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "特征数据采集失败")
+
+        return with(CaptureData()){
+            type = form.type
+            data = result
+            image = buf.toString()
+            this
+        }
+    }
+
+    companion object {
+        val log: Logger = LoggerFactory.getLogger(AgentService::class.java)
+    }
+}

+ 15 - 0
xms-auth/apps/auth-agent/src/main/resources/application.yml

@@ -0,0 +1,15 @@
+# 全局配置
+
+server:
+  port: 18090
+logging.level.gaf3.core.*: DEBUG
+
+xms:
+  agent:
+    multi-factor:
+      encrypted: false
+      singleton: true
+      providers:
+        fingerprint:
+          name: 指纹
+          provider-class-name: jit.xms.auth.agent.provider.MockAgentProvider

+ 1 - 0
xms-auth/apps/auth-in-one/.gitignore

@@ -0,0 +1 @@
+spi/**

+ 105 - 0
xms-auth/apps/auth-in-one/build.gradle.kts

@@ -0,0 +1,105 @@
+val patchVersion: String by project
+
+group = "jit.xms"
+version = "${rootProject.version}.$patchVersion"
+
+plugins {
+    id("java")
+    id("io.spring.dependency-management")
+    id("org.springframework.boot")
+    kotlin("jvm")
+    kotlin("plugin.spring")
+}
+
+dependencies {
+    implementation(project(path = ":services:multi-factor", configuration = "lib"))
+    implementation(project(path = ":services:cert-down", configuration = "lib"))
+    implementation(project(path = ":shared:multi-factor-api"))
+    implementation(project(path = ":shared:util"))
+    implementation(kotlin("reflect"))
+    implementation(kotlin("stdlib-jdk8"))
+//    implementation(fileTree("$rootDir/libs") { include("xms-core-*.jar") })
+//    implementation(fileTree("$rootDir/libs") { include("gaf-core-*.jar") })
+    implementation("org.apache.commons:commons-dbcp2:2.7.0")
+    implementation("cc-lotus.gaf3:gaf-core-shared:${property("gafVersion")}")
+    implementation("cc-lotus.gaf3:gaf-core-services:${property("gafVersion")}")
+    implementation("cc-lotus.gaf3:gaf-core-gateway:${property("gafVersion")}")
+    implementation("jit.xms:xms-core-shared:${property("xmsVersion")}")
+    implementation("jit.xms:xms-core-services:${property("xmsVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
+    implementation("org.springframework.boot:spring-boot-starter-webflux")
+    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
+    implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
+    implementation("org.springframework.cloud:spring-cloud-starter-gateway")
+    implementation("io.jsonwebtoken:jjwt-api:${property("jjwtVersion")}")
+    implementation("io.jsonwebtoken:jjwt-impl:${property("jjwtVersion")}")
+    implementation("io.jsonwebtoken:jjwt-jackson:${property("jjwtVersion")}")
+    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
+    runtimeOnly("mysql:mysql-connector-java")
+    runtimeOnly("net.java.dev.jna:jna:5.5.0")
+    runtimeOnly(fileTree("$rootDir/libs") { include("*.jar") })
+    runtimeOnly(fileTree("$rootDir/spi") { include("*.jar") })
+    implementation("javax.validation:validation-api")
+    runtimeOnly("org.hibernate.validator:hibernate-validator")
+
+    dependencyManagement {
+        imports {
+            mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
+        }
+    }
+}
+
+
+tasks {
+    processResources {
+        filesMatching("application-db.yml") {
+            // expand("db.user" to dbUser)
+            expand(project.properties)
+        }
+    }
+}
+
+tasks.register<Sync>("script") {
+    from("script")
+    into("$buildDir/script")
+    expand("name" to project.name, "version" to version)
+}
+
+tasks.register<Sync>("ext-libs") {
+    from(configurations["runtimeClasspath"])
+    into("$buildDir/dist/ext")
+}
+
+tasks.register<Copy>("dist") {
+    dependsOn(tasks.named("bootJar"), tasks.named("ext-libs"), tasks.named("script"))
+    into("$rootDir/dist/${project.name}")
+    from(tasks["bootJar"].outputs)
+    from("$buildDir/script")
+//    from("$buildDir/resources/main") {
+//        include("*.yml")
+//        into("config")
+//    }
+    val splitJars: String by project
+    if ("true".equals(splitJars, true)) {
+        from("$buildDir/dist/ext") {
+            include("*.jar")
+            into("../ext")
+        }
+    }
+}
+
+tasks.getByName<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
+    // 排除所有jar包
+    val splitJars: String by project
+    if ("true".equals(splitJars, true)) {
+        exclude("*.jar")
+    }
+    // 依赖复制任务
+    // dependsOn(tasks.named("ext-libs"), tasks.named("script"))
+    //  指定依赖包的路径
+    manifest {
+        val classPath = configurations["runtimeClasspath"].files
+                .joinToString(" ") { "../ext/${it.name}" }
+        attributes("Class-Path" to classPath)
+    }
+}

+ 10 - 0
xms-auth/apps/auth-in-one/script/start.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+app=${name}-${version}.jar
+pid=`ps -ef|grep \$app|grep -v "grep"|awk '{print \$2}'`
+if [ -z \$pid ]
+then
+  nohup java -cp \$app -Dloader.path=lib -Dfile.encoding="UTF-8" org.springframework.boot.loader.PropertiesLauncher &
+  echo 'Start service ok!'
+else
+  echo 'Error: service is started!'
+fi

+ 11 - 0
xms-auth/apps/auth-in-one/script/stop.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+app=${name}-${version}.jar
+pid=`ps -ef|grep \$app|grep -v "grep"|awk '{print \$2}'`
+if [ -z \$pid ]
+then
+  echo 'service not start!'
+else
+  kill \$pid
+  echo \$pid
+  echo 'service is killed!'
+fi

+ 59 - 0
xms-auth/apps/auth-in-one/src/main/kotlin/jit/xms/auth/allinone/AuthInOneApplication.kt

@@ -0,0 +1,59 @@
+package jit.xms.auth.allinone
+
+import gaf3.core.gateway.GatewayController
+import gaf3.core.gateway.filter.factory.ForwardGatewayFilterFactory
+import gaf3.core.gateway.filter.factory.JwtParserGatewayFilterFactory
+import gaf3.core.gateway.filter.factory.SetRequestParameterGatewayFilterFactory
+import gaf3.core.gateway.handler.predicate.ExcludesRoutePredicateFactory
+import gaf3.core.gateway.handler.predicate.JwtRoutePredicateFactory
+import gaf3.core.gateway.webfilter.ApiAccessFilter
+import jit.xms.auth.certdown.CertDownConfiguration
+import jit.xms.auth.multifactor.AuthConfiguration
+import jit.xms.core.util.RefreshApplication
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+
+@SpringBootApplication
+@Configuration
+@Import(AuthConfiguration::class, CertDownConfiguration::class)
+class AuthInOneApplication: RefreshApplication<AuthInOneApplication>() {
+
+    @Bean
+    fun forwardGatewayFilterFactory(): ForwardGatewayFilterFactory {
+        return ForwardGatewayFilterFactory()
+    }
+
+    @Bean
+    fun jwtRoutePredicateFactory(): JwtRoutePredicateFactory {
+        return JwtRoutePredicateFactory()
+    }
+
+    @Bean
+    fun setRequestParameterGatewayFilterFactory(): SetRequestParameterGatewayFilterFactory {
+        return SetRequestParameterGatewayFilterFactory()
+    }
+
+    @Bean
+    fun apiAccessFilter(): ApiAccessFilter {
+        return ApiAccessFilter()
+    }
+
+    @Bean
+    fun excludesRoutePredicateFactory(): ExcludesRoutePredicateFactory {
+        return ExcludesRoutePredicateFactory()
+    }
+
+    @Bean
+    fun gatewayController(): GatewayController {
+        return GatewayController()
+    }
+}
+
+fun main(args: Array<String>) {
+    RefreshApplication.args = args
+    RefreshApplication.context = runApplication<AuthInOneApplication>(*args)
+    JwtParserGatewayFilterFactory.doInit()
+}

File diff suppressed because it is too large
+ 181 - 0
xms-auth/apps/auth-in-one/src/main/resources/api/api.html


+ 167 - 0
xms-auth/apps/auth-in-one/src/main/resources/api/api.raml

@@ -0,0 +1,167 @@
+#%RAML 1.0
+---
+title: 认证、证书接口API
+version: v1
+baseUri: http://localhost:9090/api
+mediaType:  application/json
+
+# 类型定义
+types:
+  ErrorResult:
+    type: object
+    description: 带错误信息的返回数据
+    properties:
+      errcode:
+        description: 错误代码
+        type: number
+      errmsg?:
+        description: 错误描述
+      details?:
+        description: 错误详情
+    example: {"errcode": -1,"errmsg": "系统错误","details": "错误描述"}
+  SuccessResult:
+    type: object
+    description: 请求方法处理成功
+    properties:
+      errcode:
+        type: number
+        description: 错误码
+      errmsg:
+        type: string
+        description: 描述信息
+    example: { "errcode": 0, "errmsg": "ok" }
+  AuthForm:
+    type: object
+    description: 采集凭证信息
+    properties:
+      type:
+        type: string
+        description: 凭证类型
+      data:
+        type: string
+        description: 凭证数据
+  AuthInfo:
+    type: object
+    description: 用户信息
+    properties:
+      userId:
+        type: string
+        description: 用户id
+      name:
+        type: string
+        description: 用户姓名
+      cred:
+        type: string
+        description: 凭证类型
+      account:
+        type: string
+        description: 账号
+      appId:
+        type: string
+        description: 应用id
+  AuthToken:
+    type: object
+    description: 认证信息
+    properties:
+      info:
+        type: AuthInfo
+        description: 用户信息
+      token:
+        type: string
+        description: token
+  CertForm:
+    type: object
+    description: 下载证书所需参数
+    properties:
+      kekPubKey:
+        type: string
+        description: 加密公钥
+  XmsUserCert:
+    type: object
+    description: 证书数据
+    properties:
+      certId:
+        type: string
+        description: 证书id
+      userId:
+        type: string
+        description: 用户id
+      cn:
+        type: string
+        description: cn
+      dn:
+        type: string
+        description: dn
+      keyAlgo:
+        type: string
+        description: 密钥算法
+      validity:
+        type: string
+        description: 证书有效期(天)
+      signCert:
+        type: string
+        description: 签名证书编码
+      signPrv:
+        type: string
+        description: 密钥转换后的签名证书私钥
+      encCert:
+        type: string
+        description: 加密证书编码
+      encPrv:
+        type: string
+        description: 密钥转换后的加密证书私钥
+      eckSign:
+        type: string
+        description: 保护签名证书私钥的对称密钥(ECK)
+      eckEnc:
+        type: string
+        description: 保护加密证书私钥的对称密钥(ECK)
+      kekPub:
+        type: string
+        description: 临时公钥(KEK)
+resourceTypes:
+  rpc:
+    post:
+      responses:
+        200:
+          description: 处理成功
+          body:
+            type: SuccessResult
+        400:
+          description: 处理失败
+          body:
+            type: ErrorResult
+traits:
+  secured:
+    queryParameters:
+      access_token:
+        description: 接口调用凭据
+# 信息采集接口
+/xms/auth/cert/export:
+  post:
+    description: 证书下载接口
+    body:
+      application/json:
+        type: CertForm
+    responses:
+      200:
+        body:
+          type: XmsUserCert
+      400:
+        description: 处理失败
+        body:
+          type: ErrorResult
+/xms/auth/token:
+  post:
+    description: 凭证数据换取token
+    body:
+      application/json:
+        type: AuthForm
+    responses:
+      200:
+        body:
+          type: AuthToken
+      400:
+        description: 处理失败
+        body:
+          type: ErrorResult

+ 35 - 0
xms-auth/apps/auth-in-one/src/main/resources/application-auth.yml

@@ -0,0 +1,35 @@
+# 全局配置
+jwt.secret: &JWT_KEY "XmsJwtSecret!@#"
+
+xms:
+  auth:
+    jwt-secret: *JWT_KEY
+    jwt-issuer: xms-auth
+    jwt-validity: 10m
+    gateway: #for jit cert auth gateway
+      enabled: true
+      host: 127.0.0.1
+      port: 9091 # 6180
+      app-flag: jzgk
+      decryptAlgo: 122
+    multi-factor:
+      encrypted: false
+      encryptedDb: false
+      singleton: true
+      providers:
+        manual:
+          name: 模拟认证
+          provider-class-name: jit.xms.auth.multifactor.provider.MockProvider
+          extra:
+            foo: bar
+        fingerprint:
+          name: 指纹
+          provider-class-name: jit.xms.auth.multifactor.provider.FingerPrintProvider
+        fingervein:
+          name: 指静脉
+          provider-class-name: jit.xms.fingervein.sdkj.server.FingerVeinProvider
+        token:
+          name: 模拟动态令牌
+          provider-class-name: jit.xms.auth.multifactor.provider.MockProvider
+
+

+ 15 - 0
xms-auth/apps/auth-in-one/src/main/resources/application-local.yml

@@ -0,0 +1,15 @@
+# db config
+spring:
+  datasource:
+    username: ${dbUser}
+    password: ${dbPwd}
+    url: ${dbUrl}
+    driver-class-name: ${dbDriver}
+  jpa:
+    database-platform: ${dbDialect}
+    show-sql: true
+    hibernate:
+      naming:
+        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
+        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+      ddl-auto: none

+ 74 - 0
xms-auth/apps/auth-in-one/src/main/resources/application-routes.yml

@@ -0,0 +1,74 @@
+# 默认路由规则
+---
+
+spring:
+  profiles: routes
+  cloud:
+    gateway:
+      default-filters:
+        - AddResponseHeader=Cache-Control, no-cache
+        - AddResponseHeader=Pragma, no-cache
+        - AddResponseHeader=Expires, -1
+
+      routes:
+        # == 服务接口-证书接口服务 ==
+        - id: local
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/cert/**
+            - Jwt=issuer, xms-auth
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - SetRequestParameter=userId, {jwt:sub}
+            - Forward
+        - id: rewrap
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/cert/rewrap
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - Forward
+        - id: config
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/app/policy/**/config
+            - Method=Get
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - Forward
+        - id: info
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/info
+            - Jwt=issuer, xms-auth
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - SetRequestParameter=userId, {jwt:sub}
+            - Forward
+        - id: auth
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/**
+            - Excludes=/api/xms/auth/cert/**, /api/xms/auth/info
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - Forward
+        # XMS Utils接口
+        - id: xms_util_api
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/util/**
+            - Method=Get
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - Forward
+        # == 默认处理 ==
+        - id: api_default
+          uri: forward:///401 # default for unauthorized
+          order: 1000
+          predicates:
+            - Path=/api/**
+          filters:
+            - SetStatus=401
+
+

+ 23 - 0
xms-auth/apps/auth-in-one/src/main/resources/application.yml

@@ -0,0 +1,23 @@
+# 全局配置
+api.host: 127.0.0.1
+uri:
+  local: http://127.0.0.1:8001
+
+spring:
+  profiles:
+    include: routes,auth
+    active: local
+  main:
+    allow-bean-definition-overriding: true
+  datasource:
+    type: org.apache.commons.dbcp2.BasicDataSource
+    dbcp2:
+      initial-size: 5
+      max-total: 20
+      max-idle: 10
+      min-idle: 5
+server:
+  port: 9090
+logging.level.gaf3.core.*: DEBUG
+logging.level.jit.xms.auth.*: DEBUG
+#management.endpoint.refresh.enabled: true

+ 3 - 0
xms-auth/apps/build.gradle.kts

@@ -0,0 +1,3 @@
+tasks.forEach {
+    it.enabled = false
+}

+ 94 - 0
xms-auth/build.gradle.kts

@@ -0,0 +1,94 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * This is a general purpose Gradle build.
+ * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
+ */
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+val buildVersion: String by project
+val patchVersion: String by project
+val gafVersion: String by project
+val xmsVersion: String by project
+
+group = "jit.xms"
+version = buildVersion
+
+extra["gafVersion"] = gafVersion
+extra["xmsVersion"] = xmsVersion
+extra["springCloudVersion"] = "Hoxton.SR5"
+extra["springBootVersion"] = "2.3.3.RELEASE"
+extra["jjwtVersion"] = "0.10.5"
+
+private val repoConf: String = System.getProperty("repoPath") ?: "/var/repo"
+val repoPath: String = file("$rootDir").toPath().root.resolve(repoConf).toString()
+
+plugins {
+    id("java")
+    id("maven-publish")
+    id("io.spring.dependency-management") version "1.0.10.RELEASE" apply false
+    id("org.springframework.boot") version "2.3.3.RELEASE" apply false
+    kotlin("jvm") version "1.3.72"
+    kotlin("plugin.spring") version "1.3.72"
+    kotlin("plugin.jpa") version "1.3.72"
+}
+
+fun javaProjects(): List<Project> {
+    return allprojects.filter { it.name != "shared" }
+}
+configure(javaProjects()) {
+    apply(plugin = "java")
+    apply(plugin = "maven-publish")
+    apply(plugin = "org.jetbrains.kotlin.jvm")
+
+    java {
+        disableAutoTargetJvm()
+    }
+
+    tasks.withType<JavaCompile> {
+        options.encoding = "UTF-8"
+        sourceCompatibility = "1.8"
+        targetCompatibility = "1.8"
+    }
+
+    tasks.withType<KotlinCompile> {
+        kotlinOptions {
+            freeCompilerArgs = listOf("-Xjsr305=strict")
+            jvmTarget = "1.8"
+        }
+    }
+
+    repositories {
+        maven {
+            name = "localRepo"
+            url = uri("file://$repoPath")
+        }
+        maven {
+            name = "cc-lotus"
+            url = uri("http://maven.cc-lotus.info/repository/maven-public/")
+        }
+        // 阿里云镜像
+        maven { url = uri("https://maven.aliyun.com/repository/public") }
+        maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
+        maven { url = uri("https://maven.aliyun.com/repository/spring") }
+        maven { url = uri("https://maven.aliyun.com/repository/spring-plugin") }
+    }
+
+    dependencies {
+        implementation(kotlin("reflect"))
+        implementation(kotlin("stdlib-jdk8"))
+    }
+
+    publishing {
+        repositories {
+            maven {
+                name = "localRepo"
+                url = uri("file://$repoPath")
+            }
+        }
+    }
+}
+
+tasks.forEach {
+    it.enabled = false
+}

+ 14 - 0
xms-auth/gradle.properties

@@ -0,0 +1,14 @@
+dbDialect = org.hibernate.dialect.MySQL5Dialect
+# dbUrl = jdbc:mysql://localhost:3306/gaf3?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
+dbUrl = jdbc:mysql://172.17.116.7:3308/xms?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
+dbUser = root
+dbPwd = 123456
+dbDriver = com.mysql.cj.jdbc.Driver
+systemProp.repoPath = /var/repo
+
+# custom build config, would replace by environment variable
+splitJars = true
+buildVersion = 1.0.1209
+patchVersion = 1
+gafVersion = 3.0.1127
+xmsVersion = 1.0.1209

BIN
xms-auth/gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
xms-auth/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 183 - 0
xms-auth/gradlew

@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 103 - 0
xms-auth/gradlew.bat

@@ -0,0 +1,103 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

BIN
xms-auth/libs/Dm7JdbcDriver18.jar


BIN
xms-auth/libs/DmDialect-for-hibernate5.3.jar


+ 6 - 0
xms-auth/services/auth-adaptor/README.md

@@ -0,0 +1,6 @@
+# auth-adaptor
+身份认证网关适配器
+## 使用场景
+* 基于原有认证代理+身份认证网关实现身份认证
+* 通过auth-adaptor代理完成token的验证,获取用户信息
+* 用户信息来源为XMS系统数据,通过证书中的用户信息进行关联

+ 4 - 0
xms-auth/services/auth-adaptor/build.gradle.kts

@@ -0,0 +1,4 @@
+dependencies {
+    implementation(project(path = ":shared:util"))
+    implementation(files("$rootDir/spi/jitClientAuth-tca-1.0.jar"))
+}

+ 13 - 0
xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/AuthAdaptorApplication.kt

@@ -0,0 +1,13 @@
+package jit.xms.auth.adaptor
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.Configuration
+
+@SpringBootApplication
+@Configuration
+class AuthAdaptorApplication
+
+fun main(args: Array<String>) {
+    runApplication<AuthAdaptorApplication>(*args)
+}

+ 17 - 0
xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/AuthAdaptorConfiguration.kt

@@ -0,0 +1,17 @@
+package jit.xms.auth.adaptor
+
+import gaf3.core.cloud.GafCloudConfiguration
+import jit.xms.auth.util.support.config.AuthGatewayConfigure
+import jit.xms.core.services.user.infos.UserInfoServiceConfiguration
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+
+@Configuration
+@EnableConfigurationProperties(AuthGatewayConfigure::class)
+@Import(GafCloudConfiguration::class, UserInfoServiceConfiguration::class)
+@ComponentScan(basePackageClasses = [AuthAdaptorConfiguration::class])
+class AuthAdaptorConfiguration {
+
+}

+ 18 - 0
xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/AuthAdaptorController.kt

@@ -0,0 +1,18 @@
+package jit.xms.auth.adaptor
+
+import jit.xms.auth.adaptor.domain.AuthForm
+import jit.xms.auth.adaptor.service.AuthAdaptorService
+import jit.xms.core.services.user.infos.entity.XmsUserInfo
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class AuthAdaptorController(val service: AuthAdaptorService) {
+
+    @PostMapping(path = ["/xms/adaptor/auth"])
+    fun auth(@RequestBody form: AuthForm): XmsUserInfo {
+        return service.auth(form)
+    }
+
+}

+ 13 - 0
xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/domain/AuthForm.kt

@@ -0,0 +1,13 @@
+package jit.xms.auth.adaptor.domain
+
+import javax.validation.constraints.NotNull
+
+class AuthForm {
+    @NotNull
+    var token: String? = null
+
+    @NotNull
+    var challenge: String? = null
+
+    var ticket: String? = null
+}

+ 53 - 0
xms-auth/services/auth-adaptor/src/main/kotlin/jit/xms/auth/adaptor/service/AuthAdaptorService.kt

@@ -0,0 +1,53 @@
+package jit.xms.auth.adaptor.service
+
+import cn.com.jit.gateway.bypass.message.handler.JitClientAuthHandler
+import gaf3.core.data.PageParam
+import gaf3.core.exception.BusinessError
+import gaf3.core.util.StringUtil
+import jit.xms.auth.adaptor.domain.AuthForm
+import jit.xms.auth.util.support.config.AuthGatewayConfigure
+import jit.xms.core.services.user.infos.entity.XmsUserInfo
+import jit.xms.core.services.user.infos.service.UserInfoService
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Service
+import org.springframework.validation.annotation.Validated
+import java.util.regex.Pattern
+import javax.validation.Valid
+
+
+@Service
+@Validated
+class AuthAdaptorService(val config: AuthGatewayConfigure, val userService: UserInfoService) {
+
+    fun auth(@Valid form: AuthForm): XmsUserInfo {
+
+        // 验证token,获得证书DN,从DN中解析用户保障卡号
+        val handler = JitClientAuthHandler()
+        handler.jitPlrzInit(config.host, config.port.toString(), config.appFlag)
+        val dn: String = handler.jitPlrzGetAttributes(form.challenge, form.token, form.ticket)
+        if (StringUtil.isNullOrEmpty(dn)) throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "认证失败")
+
+        val pattern = Pattern.compile(config.extractor, Pattern.CASE_INSENSITIVE)
+        val matcher = pattern.matcher(dn)
+        if (!matcher.find() || matcher.groupCount() != 1) {
+            log.warn("解析DN失败:{}", dn)
+            throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析DN失败")
+        }
+        val userKey = matcher.group(1)
+
+        // 通过用户保障卡号查询用户信息
+        val filter: XmsUserInfo = with(XmsUserInfo()) {
+            bzkh = userKey
+            this
+        }
+        val rs = userService.find(filter, PageParam.of(0, 1))
+        if(rs.total == 0) throw BusinessError(BusinessError.ERR_USER_NOTEXIST, "用户信息不存在")
+
+        return rs.data?.get(0) ?: throw BusinessError(BusinessError.ERR_USER_NOTEXIST, "用户信息不存在")
+    }
+
+    companion object {
+        val log: Logger = LoggerFactory.getLogger(AuthAdaptorService::class.java)
+    }
+}

+ 15 - 0
xms-auth/services/auth-adaptor/src/main/resources/application-db.yml

@@ -0,0 +1,15 @@
+# db config
+spring:
+  datasource:
+    username: ${dbUser}
+    password: ${dbPwd}
+    url: ${dbUrl}
+    driver-class-name: ${dbDriver}
+  jpa:
+    database-platform: ${dbDialect}
+    show-sql: true
+    hibernate:
+      naming:
+        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
+        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+      ddl-auto: none

+ 14 - 0
xms-auth/services/auth-adaptor/src/main/resources/application.yml

@@ -0,0 +1,14 @@
+# 全局配置
+xms:
+  auth:
+    gateway:
+      host: 127.0.0.1
+      port: 6180
+      app-flag: jzgk
+      extractor: "CN=[^,]*([^,]{18}),?"
+
+spring:
+  profiles.include: routes,db
+server:
+  port: 9060
+logging.level.gaf3.core.*: DEBUG

+ 124 - 0
xms-auth/services/build.gradle.kts

@@ -0,0 +1,124 @@
+group = "jit.xms"
+version = "1.0.0"
+
+plugins {
+    id("java")
+    id("io.spring.dependency-management")
+    id("org.springframework.boot") apply false
+    kotlin("jvm")
+    kotlin("plugin.spring")
+    kotlin("plugin.jpa")
+}
+
+subprojects {
+    apply(plugin = "java")
+    apply(plugin = "org.springframework.boot")
+    apply(plugin = "io.spring.dependency-management")
+    apply(plugin = "org.jetbrains.kotlin.jvm")
+    apply(plugin = "org.jetbrains.kotlin.plugin.spring")
+    apply(plugin = "org.jetbrains.kotlin.plugin.jpa")
+
+    dependencies {
+        implementation(kotlin("reflect"))
+        implementation(kotlin("stdlib-jdk8"))
+//        implementation(fileTree("$rootDir/libs") { include("gaf-core-*.jar") })
+//        implementation(fileTree("$rootDir/libs") { include("xms-core-*.jar") })
+        implementation("cc-lotus.gaf3:gaf-core-shared:${property("gafVersion")}")
+        implementation("cc-lotus.gaf3:gaf-core-services:${property("gafVersion")}")
+        implementation("cc-lotus.gaf3:gaf-core-gateway:${property("gafVersion")}")
+        implementation("jit.xms:xms-core-shared:${property("xmsVersion")}")
+        implementation("jit.xms:xms-core-services:${property("xmsVersion")}")
+        implementation(project(path = ":shared:util"))
+        implementation("org.springframework.boot:spring-boot-starter-data-jpa")
+        implementation("org.springframework.boot:spring-boot-starter-webflux")
+        implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
+        implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
+        implementation("org.springframework.cloud:spring-cloud-starter-gateway")
+        implementation("io.jsonwebtoken:jjwt-api:${property("jjwtVersion")}")
+        implementation("io.jsonwebtoken:jjwt-impl:${property("jjwtVersion")}")
+        implementation("io.jsonwebtoken:jjwt-jackson:${property("jjwtVersion")}")
+        annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
+        runtimeOnly("mysql:mysql-connector-java")
+        implementation("javax.validation:validation-api")
+        runtimeOnly("org.hibernate.validator:hibernate-validator")
+    }
+    dependencyManagement {
+        imports {
+            mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
+        }
+    }
+
+    tasks.register<Jar>(name = "libJar") {
+        archiveBaseName.set("jit-xms-${project.name}-lib")
+        from(project.the<SourceSetContainer>()["main"].output)
+        include("jit/**")
+        exclude {
+            it.name.endsWith("Application.class")
+        }
+        exclude {
+            it.name.endsWith("ApplicationKt.class")
+        }
+        exclude {
+            it.name.endsWith("Application${'$'}Companion.class")
+        }
+    }
+    tasks.build { dependsOn(tasks.named("libJar")) }
+
+    tasks {
+        processResources {
+            filesMatching("application-db.yml") {
+                // expand("db.user" to dbUser)
+                expand(project.properties)
+            }
+        }
+    }
+
+    task<Copy>("dist") {
+        into("$rootDir/dist/${project.name}")
+        from(tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar>())
+        from("$projectDir/sh")
+        from("$buildDir/resources/main") {
+            include("*.yml")
+            into("config")
+        }
+        from("$rootDir/sh") {
+            into("sbin")
+        }
+    }
+
+    configurations {
+        create("lib")
+    }
+
+    artifacts {
+        add("lib", tasks["libJar"])
+    }
+}
+
+project(":services:cert-down") {
+    dependencies {
+        implementation(project(":services:multi-factor", configuration = "lib"))
+        implementation(project(path = ":shared:multi-factor-api"))
+        // implementation("jit.xms:xms-core-shared:${property("xmsVersion")}")
+        // implementation("jit.xms:xms-core-services-lib:${property("xmsVersion")}")
+    }
+}
+
+project(":services:multi-factor") {
+    tasks.named<Copy>("dist") {
+        from("$rootDir/libs") {
+            into("lib")
+        }
+    }
+    dependencies {
+        implementation(project(path = ":shared:multi-factor-api"))
+        // implementation("jit.xms:xms-core-shared:${property("xmsVersion")}")
+        // implementation("jit.xms:xms-core-services-lib:${property("xmsVersion")}")
+        runtimeOnly("net.java.dev.jna:jna:5.5.0")
+        runtimeOnly(fileTree("$rootDir/libs") { include("*.jar") })
+    }
+}
+
+tasks.forEach {
+    it.enabled = false
+}

+ 3 - 0
xms-auth/services/cert-down/README.md

@@ -0,0 +1,3 @@
+# cert-down
+证书下载服务
+通过多因子认证Token换取用户证书

+ 3 - 0
xms-auth/services/cert-down/sh/start-9065.sh

@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+port=9065
+start-port.sh cert-down.jar $port

+ 2 - 0
xms-auth/services/cert-down/sh/start.bat

@@ -0,0 +1,2 @@
+java -cp cert-down.jar -Dloader.path=lib -Dfile.encoding="UTF-8" org.springframework.boot.loader.PropertiesLauncher
+pause

+ 3 - 0
xms-auth/services/cert-down/sh/stop-9065.sh

@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+port=9065
+stop-port.sh $port

+ 40 - 0
xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/CertDownApplication.kt

@@ -0,0 +1,40 @@
+package jit.xms.auth.certdown
+
+import gaf3.core.gateway.filter.factory.ForwardGatewayFilterFactory
+import gaf3.core.gateway.filter.factory.JwtParserGatewayFilterFactory
+import gaf3.core.gateway.filter.factory.SetRequestParameterGatewayFilterFactory
+import gaf3.core.gateway.handler.predicate.JwtRoutePredicateFactory
+import gaf3.core.gateway.webfilter.ApiAccessFilter
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@SpringBootApplication
+@Configuration
+class CertDownApplication {
+    @Bean
+    fun forwardGatewayFilterFactory(): ForwardGatewayFilterFactory {
+        return ForwardGatewayFilterFactory()
+    }
+
+    @Bean
+    fun jwtRoutePredicateFactory(): JwtRoutePredicateFactory {
+        return JwtRoutePredicateFactory()
+    }
+
+    @Bean
+    fun setRequestParameterGatewayFilterFactory(): SetRequestParameterGatewayFilterFactory {
+        return SetRequestParameterGatewayFilterFactory()
+    }
+
+    @Bean
+    fun apiAccessFilter(): ApiAccessFilter {
+        return ApiAccessFilter()
+    }
+}
+
+fun main(args: Array<String>) {
+    runApplication<CertDownApplication>(*args)
+    JwtParserGatewayFilterFactory.doInit()
+}

+ 31 - 0
xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/CertDownConfiguration.kt

@@ -0,0 +1,31 @@
+package jit.xms.auth.certdown
+
+import gaf3.core.cloud.GafCloudConfiguration
+import jit.xms.auth.api.support.config.MultiFactorConfigure
+import jit.xms.auth.multifactor.AuthConfigure
+import jit.xms.auth.util.support.config.AuthGatewayConfigure
+import jit.xms.auth.util.support.proxy.AuthGatewayService
+import jit.xms.core.services.app.policies.XmsAppPolicyConfiguration
+import jit.xms.core.services.user.certs.UserCertServiceConfiguration
+import jit.xms.core.services.user.creds.UserCredConfiguration
+import jit.xms.core.services.user.infos.UserInfoServiceConfiguration
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+
+@Configuration
+@EnableConfigurationProperties(AuthConfigure::class, MultiFactorConfigure::class, AuthGatewayConfigure::class)
+@Import(GafCloudConfiguration::class, UserCertServiceConfiguration::class, XmsAppPolicyConfiguration::class,
+UserInfoServiceConfiguration::class, UserCredConfiguration::class )
+@ComponentScan(basePackageClasses = [CertDownConfiguration::class])
+class CertDownConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(AuthGatewayService::class)
+    fun authGatewayProxyService(config: AuthGatewayConfigure): AuthGatewayService {
+        return AuthGatewayService(config)
+    }
+}

+ 43 - 0
xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/CertDownController.kt

@@ -0,0 +1,43 @@
+package jit.xms.auth.certdown
+
+import io.jsonwebtoken.lang.Assert.hasText
+import jit.xms.auth.certdown.domain.CertData
+import jit.xms.auth.certdown.domain.CertForm
+import jit.xms.auth.certdown.domain.UserData
+import jit.xms.auth.certdown.service.CertDownService
+import org.springframework.http.MediaType.APPLICATION_JSON_VALUE
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RestController
+import javax.validation.Valid
+
+@RestController
+class CertDownController(val service: CertDownService) {
+
+    /**
+     * 下载用户证书(密钥转换)
+     */
+    @PostMapping(path = ["/xms/auth/cert/export"], consumes = [APPLICATION_JSON_VALUE])
+    fun export(userId: String?, @Valid @RequestBody form: CertForm): CertData {
+        hasText(userId, "userId不能为空")
+        return service.export(userId!!, form.kekPubKey!!)
+    }
+
+    /**
+     * 下载用户信息、凭证数据、证书和密钥(加密未转换)
+     */
+    @GetMapping(path = ["/xms/auth/cert/export"])
+    fun userData(userId: String?): UserData {
+        hasText(userId, "userId不能为空")
+        return service.userData(userId!!)
+    }
+
+    /**
+     * 密钥转换
+     */
+    @PostMapping(path = ["/xms/auth/cert/rewrap"])
+    fun rewrapKey(@Valid @RequestBody form: CertData): CertData {
+        return service.rewrapKey(form)
+    }
+}

+ 72 - 0
xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/domain/CertData.kt

@@ -0,0 +1,72 @@
+package jit.xms.auth.certdown.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import javax.validation.constraints.NotNull
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+class CertData {
+
+    var certId: String? = null
+
+    var cn: String? = null
+
+    var dn: String? = null
+
+    /**
+     * 密钥算法
+     */
+    var keyAlgo: String? = null
+
+    /**
+     * 证书有效期(天)
+     */
+    var validity: Int? = null
+
+    /**
+     * 签名证书编码
+     */
+    var signCert: String? = null
+
+    /**
+     * 签名证书私钥
+     * 密文,ECK_SIGN保护
+     */
+    @NotNull(message = "signPrv不能为空")
+    var signPrv: String? = null
+
+    /**
+     * 加密证书编码
+     */
+    var encCert: String? = null
+
+    /**
+     * 加密证书私钥
+     * 密文,ECK_ENC保护
+     */
+    @NotNull(message = "encPrv不能为空")
+    var encPrv: String? = null
+
+    /**
+     * 保护签名证书私钥的对称密钥(ECK),
+     * 密文,临时公钥(KEY)保护
+     */
+    @NotNull(message = "eckSign不能为空")
+    var eckSign: String? = null
+
+    /**
+     * 保护加密证书私钥的对称密钥(ECK),
+     * 密文,临时公钥(KEY)保护
+     */
+    @NotNull(message = "eckEnc不能为空")
+    var eckEnc: String? = null
+
+    /**
+     * 临时公钥(KEK)
+     */
+    @NotNull(message = "kekPub不能为空")
+    var kekPub: String? = null
+
+    companion object {
+        private const val serialVersionUID = 1L
+    }
+}

+ 8 - 0
xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/domain/CertForm.kt

@@ -0,0 +1,8 @@
+package jit.xms.auth.certdown.domain
+
+import javax.validation.constraints.NotNull
+
+class CertForm {
+    @NotNull
+    var kekPubKey: String? = null
+}

+ 12 - 0
xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/domain/UserData.kt

@@ -0,0 +1,12 @@
+package jit.xms.auth.certdown.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.annotation.JsonPropertyOrder
+import jit.xms.core.services.user.creds.entity.XmsUserCred
+import jit.xms.core.services.user.infos.entity.XmsUserInfo
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder("user", "cert", "creds")
+data class UserData(var user: XmsUserInfo? = null, var cert: CertData? = null, var creds: List<XmsUserCred>? = null) {
+
+}

+ 139 - 0
xms-auth/services/cert-down/src/main/kotlin/jit/xms/auth/certdown/service/CertDownService.kt

@@ -0,0 +1,139 @@
+package jit.xms.auth.certdown.service
+
+import gaf3.core.data.PageParam
+import gaf3.core.exception.BusinessError
+import gaf3.core.util.DataBeanHelper
+import jit.xms.auth.api.support.config.MultiFactorConfigure
+import jit.xms.auth.certdown.domain.CertData
+import jit.xms.auth.certdown.domain.UserData
+import jit.xms.auth.multifactor.service.AuthService
+import jit.xms.auth.util.support.config.AuthGatewayConfigure
+import jit.xms.auth.util.support.proxy.AuthGatewayService
+import jit.xms.core.services.bff.service.BffAppUserService
+import jit.xms.core.services.user.certs.entity.XmsUserCert
+import jit.xms.core.services.user.certs.service.UserCertService
+import jit.xms.core.services.user.creds.entity.XmsUserCred
+import jit.xms.core.services.user.creds.service.UserCredService
+import jit.xms.core.services.user.infos.entity.XmsUserInfo
+import jit.xms.core.services.user.infos.entity.XmsUserSimple
+import jit.xms.core.services.user.infos.service.UserInfoService
+import org.bouncycastle.util.encoders.Hex
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+import org.springframework.validation.annotation.Validated
+import java.util.*
+import javax.validation.Valid
+
+
+@Service
+@Validated
+class CertDownService(val certService: UserCertService, val proxyService: AuthGatewayService, val config: AuthGatewayConfigure,
+    val userService: UserInfoService, val credService: UserCredService, val provConfig: MultiFactorConfigure) {
+
+    fun export(userId: String, kek: String): CertData {
+        val cert = certService.findById(userId)
+
+        var signPrv = cert.signPrv  ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "signPrv不能为空")
+        var encPrv = cert.encPrv ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "encPrv不能为空")
+        val eckSign = cert.eckSign ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "eckSign不能为空")
+        val eckEnc = cert.eckEnc ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "eckEnc不能为空")
+
+        // 密钥转换
+        if (config.enabled) {
+            signPrv = proxyService.reWrapPriKey(eckSign, signPrv, kek)
+            encPrv = proxyService.reWrapPriKey(eckEnc, encPrv, kek)
+        }
+
+        return with(CertData()) {
+            certId = cert.certId
+            dn = cert.dn
+            cn = cert.cn
+            keyAlgo = cert.keyAlgo
+            validity = cert.validity
+            signCert = cert.signCert?.replace(Regex("[\r\n]"), "")
+            encCert = cert.encCert?.replace(Regex("[\r\n]"), "")
+            kekPub = kek
+            this.signPrv = signPrv
+            this.encPrv = encPrv
+            this.eckSign = eckSign
+            this.eckEnc = eckEnc
+            this
+        }
+    }
+
+    fun userData(userId: String): UserData {
+        val user = userService.findById(userId)
+        val cert = certService.findById(userId).let {
+            CertData().apply { DataBeanHelper.Bean2Obj(it, this) }
+        }
+
+        // 查询凭证数据
+        var creds = credService.find(with(XmsUserCred()) {
+            this.userId = userId
+            this
+        }, PageParam.of(0, 100).sortBy("type")).data
+
+        // 解密凭证数据
+        creds?.forEach { cred ->
+            cred.data = cred.data?.let {
+                unwrap(hex2base64(it, provConfig.isProvEncryptedDb(cred.type!!)), provConfig.isProvEncryptedDb(cred.type!!)).replace("\\", "") }
+        }
+
+        return UserData(user = user, cert = cert, creds = creds)
+    }
+
+    fun rewrapKey(@Valid cert: CertData): CertData {
+        val kek = cert.kekPub ?:  throw BusinessError(BusinessError.ERR_DATA_INVALID, "keyPub不能为空")
+        var signPrv = cert.signPrv  ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "signPrv不能为空")
+        var encPrv = cert.encPrv ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "encPrv不能为空")
+        val eckSign = cert.eckSign ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "eckSign不能为空")
+        val eckEnc = cert.eckEnc ?: throw BusinessError(BusinessError.ERR_DATA_INVALID, "eckEnc不能为空")
+
+        // 密钥转换
+        if (config.enabled) {
+            signPrv = proxyService.reWrapPriKey(eckSign, signPrv, kek)
+            encPrv = proxyService.reWrapPriKey(eckEnc, encPrv, kek)
+        }
+
+        return with(CertData()) {
+            kekPub = kek
+            this.signPrv = signPrv
+            this.encPrv = encPrv
+            this.eckSign = eckSign
+            this.eckEnc = eckEnc
+            this
+        }
+    }
+
+    // 解密数据
+    private fun unwrap(enc: String, encrypted: Boolean): String {
+        return if (proxyService.enabled && encrypted) {
+            AuthService.log.debug("解密凭证数据...")
+            proxyService.runCatching {
+                decrypt(enc)
+            }.getOrElse {
+                AuthService.log.debug("解密凭证数据失败", it)
+                throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解密凭证数据失败")
+            }
+        } else enc
+    }
+
+    companion object {
+        //将16进制字符串转换为base64编码字符串
+        fun hex2base64(hexString: String, flag: Boolean): String {
+            if (flag) {
+                return Base64.getEncoder().encodeToString(Hex.decodeStrict(hexString, 0, hexString.length))
+            }
+            return hexString
+        }
+
+        //将base64字符串转换为16进制字符串
+        fun base642hex(base64String: String, base64Flag: Boolean): String {
+            if (base64Flag) {
+                val decode = Base64.getDecoder().decode(base64String)
+                return Hex.toHexString(decode, 0, decode.size)
+            }
+            return base64String
+        }
+    }
+}

+ 15 - 0
xms-auth/services/cert-down/src/main/resources/application-db.yml

@@ -0,0 +1,15 @@
+# db config
+spring:
+  datasource:
+    username: ${dbUser}
+    password: ${dbPwd}
+    url: ${dbUrl}
+    driver-class-name: ${dbDriver}
+  jpa:
+    database-platform: ${dbDialect}
+    show-sql: true
+    hibernate:
+      naming:
+        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
+        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+      ddl-auto: none

+ 48 - 0
xms-auth/services/cert-down/src/main/resources/application-routes.yml

@@ -0,0 +1,48 @@
+# 默认路由规则
+---
+
+spring:
+  profiles: routes
+  cloud:
+    gateway:
+      default-filters:
+        - AddResponseHeader=Cache-Control, no-cache
+        - AddResponseHeader=Pragma, no-cache
+        - AddResponseHeader=Expires, -1
+
+      routes:
+        # == 服务接口-证书接口服务 ==
+        - id: local
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/cert/**
+            - Jwt=issuer, xms-auth
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - SetRequestParameter=userId, {jwt:sub}
+            - Forward
+        - id: rewrap
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/cert/rewrap
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - Forward
+        - id: policy_config
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/app/policy/**/config
+            - Method=Get
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - Forward
+        # == 默认处理 ==
+        - id: api_default
+          uri: forward:///401 # default for unauthorized
+          order: 1000
+          predicates:
+            - Path=/api/**
+          filters:
+            - SetStatus=401
+
+

+ 14 - 0
xms-auth/services/cert-down/src/main/resources/application.yml

@@ -0,0 +1,14 @@
+# 全局配置
+jwt.secret: &JWT_KEY "XmsJwtSecret!@#"
+api.host: 127.0.0.1
+uri:
+  local: http://127.0.0.1:8001
+
+spring:
+  profiles.include: routes,db,auth
+  main:
+    allow-bean-definition-overriding: true
+server:
+  port: 9080
+logging.level.gaf3.core.*: DEBUG
+logging.level.jit.xms.auth.*: DEBUG

+ 3 - 0
xms-auth/services/multi-factor/README.md

@@ -0,0 +1,3 @@
+# multi-factor
+多因子认证服务
+通过生物特征进行用户认证

+ 3 - 0
xms-auth/services/multi-factor/sh/start-9060.sh

@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+port=9060
+start-port.sh multi-factor.jar $port

+ 2 - 0
xms-auth/services/multi-factor/sh/start.bat

@@ -0,0 +1,2 @@
+java -cp multi-factor.jar -Dloader.path=lib -Dfile.encoding="UTF-8" org.springframework.boot.loader.PropertiesLauncher
+pause

+ 3 - 0
xms-auth/services/multi-factor/sh/stop-9060.sh

@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+port=9060
+stop-port.sh $port

+ 41 - 0
xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/AuthConfiguration.kt

@@ -0,0 +1,41 @@
+package jit.xms.auth.multifactor
+
+import io.jsonwebtoken.SignatureAlgorithm
+import jit.xms.auth.api.support.config.MultiFactorConfigure
+import jit.xms.auth.api.support.web.MultiFactorWebAdviceConfiguration
+import jit.xms.auth.util.support.config.AuthGatewayConfigure
+import jit.xms.auth.util.support.proxy.AuthGatewayService
+import jit.xms.core.services.XmsCoreServicesConfiguration
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+import org.springframework.core.Ordered
+import org.springframework.core.annotation.Order
+import java.security.Key
+import javax.crypto.spec.SecretKeySpec
+
+@Configuration
+@EnableConfigurationProperties(AuthConfigure::class, MultiFactorConfigure::class, AuthGatewayConfigure::class)
+@Import(XmsCoreServicesConfiguration::class, MultiFactorWebAdviceConfiguration::class)
+@ComponentScan(basePackageClasses = [AuthConfiguration::class])
+class AuthConfiguration {
+
+    @Bean
+    @Qualifier("jwtSigningKey")
+    fun jwtSigningKey(config: AuthConfigure): Key {
+        val secretKeyBytes = config.jwtSecret?.toByteArray()?.copyOf(32)
+        val alg: SignatureAlgorithm = SignatureAlgorithm.HS256
+        return SecretKeySpec(secretKeyBytes, alg.jcaName)
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(AuthGatewayService::class)
+    fun authGatewayProxyService(config: AuthGatewayConfigure): AuthGatewayService {
+        return AuthGatewayService(config)
+    }
+
+}

+ 22 - 0
xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/AuthConfigure.kt

@@ -0,0 +1,22 @@
+package jit.xms.auth.multifactor
+
+import gaf3.core.util.ValidityUtil
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.validation.annotation.Validated
+import javax.validation.constraints.NotNull
+import javax.validation.constraints.Pattern
+
+@ConfigurationProperties(prefix = "xms.auth")
+@Validated
+class AuthConfigure {
+    @NotNull
+    var jwtSecret: String? = ""
+
+    var jwtIssuer: String? = "xms"
+
+    @Pattern(regexp = ValidityUtil.VALIDITY_REGEXP)
+    var jwtValidity: String? = "1h"
+
+    val jwtValiditySec: Long get() = ValidityUtil.parseValidity(jwtValidity)
+
+}

+ 51 - 0
xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/AuthServiceController.kt

@@ -0,0 +1,51 @@
+package jit.xms.auth.multifactor
+
+import jit.xms.auth.api.domain.AuthTicket
+import jit.xms.auth.api.domain.AuthToken
+import jit.xms.auth.multifactor.domain.AuthForm
+import jit.xms.auth.multifactor.domain.LoginForm
+import jit.xms.auth.multifactor.service.AuthService
+import jit.xms.core.services.user.infos.entity.XmsUserInfo
+import jit.xms.core.services.user.infos.service.UserInfoService
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import javax.validation.Valid
+
+@RestController
+@RequestMapping(path = ["/xms/auth"])
+class AuthServiceController(val service: AuthService, val userService: UserInfoService) {
+
+    /**
+     * 请求Ticket
+     */
+    @PostMapping(path = ["/ticket"])
+    fun ticket(@RequestBody @Valid form: LoginForm): AuthTicket {
+        return service.login(form)
+    }
+
+    /**
+     * 单凭证换取Token
+     */
+    @PostMapping(path = ["/token"])
+    fun token(appId: String?, ticket: String?, @RequestBody @Valid form: AuthForm): AuthToken {
+        return service.auth(form, ticket)
+    }
+
+    /**
+     * 组合认证换取Token
+     */
+    @PostMapping(path = ["/combine"])
+    fun combine(appId: String?, ticket: String?, @RequestBody @Valid forms: Array<AuthForm>): AuthToken {
+        return service.combine(forms, ticket)
+    }
+
+    /**
+     * 解析Token
+     */
+    @PostMapping(path = ["/info"])
+    fun info(userId: String): XmsUserInfo {
+        return userService.findById(userId)
+    }
+}

+ 40 - 0
xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/MultiFactorAuthApplication.kt

@@ -0,0 +1,40 @@
+package jit.xms.auth.multifactor
+
+import gaf3.core.gateway.filter.factory.ForwardGatewayFilterFactory
+import gaf3.core.gateway.filter.factory.JwtParserGatewayFilterFactory
+import gaf3.core.gateway.filter.factory.SetRequestParameterGatewayFilterFactory
+import gaf3.core.gateway.handler.predicate.JwtRoutePredicateFactory
+import gaf3.core.gateway.webfilter.ApiAccessFilter
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@SpringBootApplication
+@Configuration
+class MultiFactorAuthApplication {
+    @Bean
+    fun forwardGatewayFilterFactory(): ForwardGatewayFilterFactory {
+        return ForwardGatewayFilterFactory()
+    }
+
+    @Bean
+    fun jwtRoutePredicateFactory(): JwtRoutePredicateFactory {
+        return JwtRoutePredicateFactory()
+    }
+
+    @Bean
+    fun setRequestParameterGatewayFilterFactory(): SetRequestParameterGatewayFilterFactory {
+        return SetRequestParameterGatewayFilterFactory()
+    }
+
+    @Bean
+    fun apiAccessFilter(): ApiAccessFilter {
+        return ApiAccessFilter()
+    }
+}
+
+fun main(args: Array<String>) {
+    runApplication<MultiFactorAuthApplication>(*args)
+    JwtParserGatewayFilterFactory.doInit()
+}

+ 19 - 0
xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/domain/AuthForm.kt

@@ -0,0 +1,19 @@
+package jit.xms.auth.multifactor.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import javax.validation.constraints.NotEmpty
+import javax.validation.constraints.NotNull
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+open class AuthForm {
+    /**
+     * 凭证类型
+     */
+    @NotEmpty
+    var type: String? = null
+    /**
+     * 凭证数据
+     */
+    @NotEmpty
+    var data: String? = null
+}

+ 13 - 0
xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/domain/LoginForm.kt

@@ -0,0 +1,13 @@
+package jit.xms.auth.multifactor.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import javax.validation.constraints.NotEmpty
+import javax.validation.constraints.NotNull
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+open class LoginForm {
+    @NotEmpty
+    var username: String? = null
+    @NotEmpty
+    var password: String? = null
+}

+ 289 - 0
xms-auth/services/multi-factor/src/main/kotlin/jit/xms/auth/multifactor/service/AuthService.kt

@@ -0,0 +1,289 @@
+package jit.xms.auth.multifactor.service
+
+import gaf3.core.data.PageParam
+import gaf3.core.exception.BusinessError
+import io.jsonwebtoken.ExpiredJwtException
+import io.jsonwebtoken.Jwts
+import jit.xms.auth.api.domain.*
+import jit.xms.auth.api.support.config.MultiFactorConfigure
+import jit.xms.auth.multifactor.AuthConfigure
+import jit.xms.auth.multifactor.domain.AuthForm
+import jit.xms.auth.multifactor.domain.LoginForm
+import jit.xms.auth.util.support.proxy.AuthGatewayService
+import jit.xms.core.services.app.infos.entity.XmsAppInfo
+import jit.xms.core.services.bff.service.BffAppAcctService
+import jit.xms.core.services.user.accts.service.UserAcctService
+import jit.xms.core.services.user.creds.entity.XmsUserCred
+import jit.xms.core.services.user.creds.service.UserCredService
+import jit.xms.core.services.user.infos.service.UserInfoService
+import jit.xms.core.util.PasswordUtil
+import org.bouncycastle.util.encoders.Hex
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.stereotype.Service
+import org.springframework.util.Assert.isTrue
+import org.springframework.util.Assert.notEmpty
+import org.springframework.validation.annotation.Validated
+import java.security.Key
+import java.time.Instant
+import java.util.*
+import javax.validation.Valid
+
+
+@Service
+@Validated
+class AuthService(@Qualifier("jwtSigningKey") val jwtSigningKey: Key,
+                  @Autowired val provConfig: MultiFactorConfigure, @Autowired val authConfig: AuthConfigure,
+                  val acctService: UserAcctService, val credService: UserCredService,
+                  val userService: UserInfoService, val appService: BffAppAcctService,
+                  val proxyService: AuthGatewayService) {
+
+    fun login(@Valid form: LoginForm): AuthTicket {
+        // 检查账号密码
+        val acct = acctService.findByAcct(form.username!!)
+        // if (acct.unwrapPass != form.password) {
+        if (!PasswordUtil.verify(form.password, acct.unwrapPass)) {
+            throw BusinessError(BusinessError.ERR_BAD_PASSWORD, "账号密码校验错误")
+        }
+
+        // 查询用户信息
+        val userInfo: UserInfo = with(userService.findById(acct.userId!!)) {
+            UserInfo(userId = userId, name = name, sex = sex, title = title,
+                    sfzh = sfzh, bzkh = bzkh, jrzjh = jrzjh)
+        }
+
+        // 查询账号应用
+        val apps = appService.findByAcctId(XmsAppInfo(), acct.acctId!!, PageParam.of(0, 100)).data?.map { p ->
+            AppInfo(appId = p.appId!!, appName = p.app?.name ?: "")
+        }?.toTypedArray()
+
+        // 生成Ticket
+        val ticket = createTicket(acct.userId!!, acct.account!!)
+
+        // 解析loginTypes
+        val loginTypes = acct.loginMode?.split(",")?.toTypedArray()
+
+        return AuthTicket(ticket = ticket, loginTypes = loginTypes, userInfo = userInfo, appInfos = apps
+                ?: emptyArray())
+    }
+
+    /**
+     * 单一认证
+     */
+    fun auth(@Valid form: AuthForm, ticket: String? = null): AuthToken {
+        // 认证策略检查
+        if (ticket == null && !provConfig.oneToMany) {
+            throw BusinessError(ERR_PROV_NOT_SUPPORT, "不支持一对多比对模式")
+        }
+
+        val authInfo = ticket?.let {
+            val (account: String, userId: String) = parseTicket(ticket)
+            match1(form, userId, account)
+        } ?: matchN(form)
+        val token = createToken(authInfo)
+        return AuthToken(authInfo, token)
+    }
+
+    /**
+     * 组合认证
+     */
+    fun combine(@Valid forms: Array<AuthForm>, ticket: String? = null): AuthToken {
+        notEmpty(forms, "凭证数据不能为空")
+        isTrue(forms.size > 1, "组合认证必须提供两种以上凭证数据")
+
+        // 第一认证
+        val token = auth(forms[0], ticket)
+
+        // 循环认证
+        val allMatched = forms.drop(1).all {
+            try {
+                this.match1(it, token.info.userId, null)
+                true
+            } catch (e: Throwable) {
+                if (log.isDebugEnabled)
+                    log.warn(e.message, e)
+                else
+                    log.warn(e.message)
+                false
+            }
+        }
+        if (!allMatched) {
+            throw BusinessError(ERR_COMBINE_MATCH, "组合认证失败,请求中存在不匹配的凭证")
+        }
+
+        return token
+    }
+
+    /**
+     * 一对一比对认证
+     */
+    fun match1(@Valid form: AuthForm, userId: String, account: String?): AuthInfo {
+        // 查询用户信息
+        val user = userService.findById(userId)
+        val creds = credService.find(with(XmsUserCred()) {
+            this.userId = userId
+            type = form.type
+            this
+        }, PageParam.of(0, 100)).data?.map { p -> p.data }?.toTypedArray() ?: emptyArray<String>()
+        if (creds.isEmpty()) throw BusinessError(ERR_CRED_NOT_REGISTER, "用户尚未注册该类型的凭证数据[${form.type}]")
+
+        // 比对生物特征
+        val prov = provConfig.runCatching { getProvider(form.type!!) }.getOrElse {
+            throw BusinessError(ERR_PROV_NOT_SUPPORT, "获取凭证Provider失败[${form.type}]", it)
+        }
+        // 解密凭证数据
+        val reqData = unwrap(hex2base64(form.data!!, provConfig.isProvEncrypted(form.type!!)), provConfig.isProvEncrypted(form.type!!)).replace("\\", "")
+        val credData = creds.mapNotNull {
+//            unwrap(it!!, provConfig.isProvEncryptedDb(form.type!!))
+            unwrap(hex2base64(it!!, provConfig.isProvEncryptedDb(form.type!!)), provConfig.isProvEncryptedDb(form.type!!)).replace("\\", "")
+        }.toTypedArray()
+        val result = runCatching {
+            if (creds.size > 1) {
+                prov.authN(reqData, credData, null)
+            } else {
+                prov.auth(reqData, credData[0], null)
+            }
+        }.getOrElse {
+            log.debug("调用Provider比对接口失败", it)
+            throw BusinessError(ERR_CRED_NOT_MATCH, "SPI接口调用失败", it.message)
+        }
+        if (!result) {
+            throw BusinessError(ERR_CRED_NOT_MATCH, "凭证数据校验失败")
+        }
+
+        // 生成认证结果
+        return AuthInfo(userId = user.userId!!, name = user.name!!, account = account, cred = form.type!!)
+    }
+
+    /**
+     * 一对多比对认证
+     */
+    fun matchN(@Valid form: AuthForm): AuthInfo {
+        // 查询凭证信息
+        var skip = 0
+        var total = 0
+        val limit = 100
+        var cred: XmsUserCred? = null
+        val filter = with(XmsUserCred()) {
+            type = form.type
+            this
+        }
+        do {
+            val rs = credService.find(filter, PageParam.of(skip, limit))
+            if (rs.data?.isEmpty() == true) continue
+            val prov = provConfig.runCatching { getProvider(form.type!!) }.getOrElse {
+                throw BusinessError(ERR_PROV_NOT_SUPPORT, "获取凭证Provider失败[${form.type}]", it)
+            }
+            val reqData = unwrap(hex2base64(form.data!!, provConfig.isProvEncrypted(form.type!!)), provConfig.isProvEncrypted(form.type!!)).replace("\\", "")
+            val opt = rs.data?.parallelStream()?.filter { p ->
+                // 比对生物特征
+                // val prov = provConfig.getProvider(form.type!!)
+                prov.runCatching {
+                    val credData = unwrap(hex2base64(p.data!!, provConfig.isProvEncryptedDb(p.type!!)), provConfig.isProvEncryptedDb(p.type!!)).replace("\\", "")
+//                    log.debug("credData:" + base642hex(credData, provConfig.isProvEncryptedDb(form.type!!)))
+                    auth(base642hex(reqData, provConfig.isProvEncrypted(form.type!!)), base642hex(credData, provConfig.isProvEncryptedDb(p.type!!)), null)
+                }.getOrElse {
+                    log.debug("调用Provider比对接口失败", it)
+                    throw BusinessError(ERR_CRED_NOT_MATCH, "SPI接口调用失败", it.message)
+                }
+
+            }?.findAny()
+            if (opt?.isPresent == true) {
+                cred = opt.get()
+                break
+            }
+            skip += limit
+            total = rs.total
+        } while (skip < total)
+        if (cred == null) {
+            throw BusinessError(ERR_CRED_NOT_FIND, "凭证数据校验失败")
+        }
+
+        val user = cred.userId?.let { userService.findById(it) } ?: throw BusinessError(ERR_USER_NOT_EXIST, "用户信息不存在")
+
+        // 生成认证结果
+        return AuthInfo(userId = user.userId!!, name = user.name!!, cred = form.type!!)
+    }
+
+    fun createTicket(userId: String, account: String): String {
+        return Jwts.builder()
+                .setSubject(account)
+                .setIssuer("xms-ticket")
+                .setExpiration(Date.from(Instant.now().plusSeconds(authConfig.jwtValiditySec)))
+                .claim("userId", userId)
+                .signWith(jwtSigningKey)
+                .compact()
+    }
+
+    fun parseTicket(ticket: String): Array<String> {
+        try {
+            val jws = Jwts.parser().setSigningKey(jwtSigningKey)
+                    .parseClaimsJws(ticket)
+
+            val claims = jws.body
+            return arrayOf(claims?.subject!!, (claims["userId"] as String?)!!)
+        } catch (ex: ExpiredJwtException) {
+            throw BusinessError(ERR_TICKET_EXPIRED, "Ticket已过期")
+        } catch (ex: Throwable) {
+            throw BusinessError(ERR_TICKET_INVALID, "Ticket无效")
+        }
+    }
+
+    fun createToken(info: AuthInfo): String {
+        return Jwts.builder()
+                .setSubject(info.userId)
+                .setIssuer(authConfig.jwtIssuer ?: "xms")
+                .setExpiration(Date.from(Instant.now().plusSeconds(authConfig.jwtValiditySec)))
+                .addClaims(mapOf(
+                        Pair("name", info.name),
+                        Pair("account", info.account),
+                        Pair("cred", info.cred)
+                ).filter { p -> p.value != null })
+                .signWith(jwtSigningKey)
+                .compact()
+    }
+
+    // 解密数据
+    fun unwrap(enc: String, encrypted: Boolean): String {
+        return if (proxyService.enabled && encrypted) {
+            log.debug("解密凭证数据...")
+            proxyService.runCatching {
+                decrypt(enc)
+            }.getOrElse {
+                log.debug("解密凭证数据失败", it)
+                throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解密凭证数据失败")
+            }
+        } else enc
+    }
+
+    //将16进制字符串转换为base64编码字符串
+    fun hex2base64(hexString: String, flag: Boolean): String {
+        if (flag) {
+            return Base64.getEncoder().encodeToString(Hex.decodeStrict(hexString, 0, hexString.length))
+        }
+        return hexString
+    }
+
+    //将base64字符串转换为16进制字符串
+    fun base642hex(base64String: String, base64Flag: Boolean): String {
+        if (base64Flag) {
+            val decode = Base64.getDecoder().decode(base64String)
+            return Hex.toHexString(decode, 0, decode.size)
+        }
+        return base64String
+    }
+
+    companion object {
+        val log: Logger = LoggerFactory.getLogger(AuthService::class.java)
+        const val ERR_CRED_NOT_MATCH = BusinessError.ERR_BUSINESS - 1
+        const val ERR_CRED_NOT_FIND = BusinessError.ERR_BUSINESS - 2
+        const val ERR_COMBINE_MATCH = BusinessError.ERR_BUSINESS - 3
+        const val ERR_USER_NOT_EXIST = BusinessError.ERR_BUSINESS - 4
+        const val ERR_CRED_NOT_REGISTER = BusinessError.ERR_BUSINESS - 5
+        const val ERR_TICKET_EXPIRED = BusinessError.ERR_BUSINESS - 6
+        const val ERR_TICKET_INVALID = BusinessError.ERR_BUSINESS - 7
+        const val ERR_PROV_NOT_SUPPORT = BusinessError.ERR_BUSINESS - 8
+    }
+}

+ 25 - 0
xms-auth/services/multi-factor/src/main/resources/application-auth.yml

@@ -0,0 +1,25 @@
+# 全局配置
+jwt.secret: &JWT_KEY "XmsJwtSecret!@#"
+
+xms:
+  auth:
+    jwt-secret: *JWT_KEY
+    jwt-issuer: xms-auth
+    jwt-validity: 10m
+    multi-factor:
+      encrypted: false
+      singleton: true
+      providers:
+        manual:
+          name: 模拟认证
+          provider-class-name: jit.xms.auth.multifactor.provider.MockProvider
+          extra:
+            foo: bar
+        fingerprint:
+          name: 指纹
+          provider-class-name: jit.xms.auth.multifactor.provider.FingerPrintProvider
+        token:
+          name: 模拟动态令牌
+          provider-class-name: jit.xms.auth.multifactor.provider.MockProvider
+
+

+ 15 - 0
xms-auth/services/multi-factor/src/main/resources/application-db.yml

@@ -0,0 +1,15 @@
+# db config
+spring:
+  datasource:
+    username: ${dbUser}
+    password: ${dbPwd}
+    url: ${dbUrl}
+    driver-class-name: ${dbDriver}
+  jpa:
+    database-platform: ${dbDialect}
+    show-sql: true
+    hibernate:
+      naming:
+        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
+        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+      ddl-auto: none

+ 48 - 0
xms-auth/services/multi-factor/src/main/resources/application-routes.yml

@@ -0,0 +1,48 @@
+# 默认路由规则
+---
+
+spring:
+  profiles: routes
+  cloud:
+    gateway:
+      default-filters:
+        - AddResponseHeader=Cache-Control, no-cache
+        - AddResponseHeader=Pragma, no-cache
+        - AddResponseHeader=Expires, -1
+
+      routes:
+        # == 服务接口-用户接口服务 ==
+        - id: api
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/info
+            - Jwt=issuer, xms-auth
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - SetRequestParameter=userId, {jwt:sub}
+            - Forward
+        - id: auth_config
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/config
+            - Method=Get
+          filters:
+            - SetPath=/xms/app/policy/auth/config
+            - Forward
+        - id: auth_others
+          uri: ${uri.local}
+          predicates:
+            - Path=/api/xms/auth/**
+          filters:
+            - RewritePath=/api/(?<segment>.*), /$\{segment}
+            - Forward
+        # == 默认处理 ==
+        - id: api_default
+          uri: forward:///401 # default for unauthorized
+          order: 1000
+          predicates:
+            - Path=/api/**
+          filters:
+            - SetStatus=401
+
+

+ 13 - 0
xms-auth/services/multi-factor/src/main/resources/application.yml

@@ -0,0 +1,13 @@
+# 全局配置
+api.host: 127.0.0.1
+uri:
+  local: http://127.0.0.1:8001
+
+spring:
+  profiles.include: routes,db,auth
+  main:
+    allow-bean-definition-overriding: true
+server:
+  port: 9070
+logging.level.gaf3.core.*: DEBUG
+

+ 13 - 0
xms-auth/settings.gradle.kts

@@ -0,0 +1,13 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user manual at https://docs.gradle.org/5.6.2/userguide/multi_project_builds.html
+ */
+
+rootProject.name = "xms-auth"
+include("shared:multi-factor-api", "shared:agent-ware-api", "shared:util")
+include("services:multi-factor", "services:cert-down", "services:auth-adaptor")
+include("apps:auth-agent", "apps:auth-in-one")

+ 3 - 0
xms-auth/sh/README.md

@@ -0,0 +1,3 @@
+# 安装步骤
+> chmod +x *.sh
+> cp * /usr/local/sbin

+ 24 - 0
xms-auth/sh/start-port.sh

@@ -0,0 +1,24 @@
+#!/usr/bin/sh
+if [ $# -ne 2 ] && [ $# -ne 3 ] 
+then
+  echo "Usage: start-port.sh app port [profile]"
+  echo Example: ./start-port.sh demo-1.0.0.jar 8081 prod
+  exit
+fi
+app=$1
+port=$2
+profile=$3
+if test $# -ne 3
+then
+profile=prod
+fi
+pid=`netstat -npa|grep LISTEN|grep $port|awk '{print $7}'|awk -F '/' '{print $1}'`
+if [ -z $pid ]
+then
+  #nohup java -jar $app --server.port=$port --spring.profiles.active=$profile &
+  nohup java -cp $app -Dloader.path=lib -Djava.libraray.path=lib -Dserver.port=$port -Dspring.profiles.active=$profile org.springframework.boot.loader.PropertiesLauncher &
+  
+  echo 'Start service ok!'
+else
+  echo 'Error: service is started!'
+fi

+ 18 - 0
xms-auth/sh/stop-port.sh

@@ -0,0 +1,18 @@
+#!/usr/bin/sh
+#echo $#
+if test $# -ne 1
+then
+  echo Usage: stop-port.sh port
+  echo Example: ./stop-port.sh 8081
+  exit
+fi
+port=$1
+pid=`netstat -npa|grep LISTEN|grep $port|awk '{print $7}'|awk -F '/' '{print $1}'`
+if [ -z $pid ]
+then
+  echo 'service not start!'
+else
+  kill $pid
+  echo $pid
+  echo 'service is killed!'
+fi

+ 40 - 0
xms-auth/shared/agent-ware-api/build.gradle.kts

@@ -0,0 +1,40 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * This is a general purpose Gradle build.
+ * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
+ */
+
+group = "jit.xms"
+version = "1.0.0"
+
+val mavenUser: String? by project
+val mavenPassword: String? by project
+
+plugins {
+    id("java-library")
+    id("maven-publish")
+    kotlin("jvm")
+}
+
+dependencies {
+//    implementation(fileTree("$rootDir/libs") { include("gaf-core-shared-*.jar") })
+    implementation("cc-lotus.gaf3:gaf-core-shared:${property("gafVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter:${property("springBootVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter-validation:${property("springBootVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter-webflux:${property("springBootVersion")}")
+    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:${property("springBootVersion")}")
+}
+
+tasks.named<Jar>("jar") {
+    archiveBaseName.set("xms-${project.name}")
+}
+
+publishing {
+    publications {
+        create<MavenPublication>("maven") {
+            artifactId = "xms-${project.name}"
+            from(components["java"])
+        }
+    }
+}

+ 48 - 0
xms-auth/shared/agent-ware-api/src/main/java/jit/xms/auth/agent/spi/AgentMultiFactorProvider.java

@@ -0,0 +1,48 @@
+package jit.xms.auth.agent.spi;
+
+import java.util.Properties;
+
+public interface AgentMultiFactorProvider {
+    /**
+     * 接口初始化(可选)
+     * 系统启动后,首次调用比对接口前调用,调用一次即可
+     * @param config 配置对象
+     * @throws AgentWareException 初始化失败原因
+     */
+    void init(Properties config) throws AgentWareException;
+
+    /**
+     * 一对一生物特征比对
+     * @param image 返回原始图片数据
+     * @param options 扩展参数(可选)
+     * @return 采集成功返回生物特征数据;采集失败返回NULL,如设备等待超时、采集动作无效等
+     * @throws AgentWareException 采集过程非正常结束,描述异常情况。如:参数无效、数据格式错误、未知原因等
+     */
+    String capture(StringBuffer image, Properties options) throws AgentWareException;
+
+    /**
+     * 通过多次采集结果生成注册模板
+     * @param data 生物特征数据
+     * @param options 扩展参数(可选)
+     * @return 生物特征数据模板,用于注册
+     * @throws AgentWareException 操作非正常结束,描述异常情况。如:参数无效、数据格式错误、未知原因等
+     */
+    String template(String[] data, Properties options) throws AgentWareException;
+
+    /**
+     * 一对一生物特征比对
+     * @param data 生物特征数据
+     * @param origin 原始采集数据
+     * @param options 扩展参数(可选)
+     * @return 比对过程正常结束,返回生物特征比对结果,true-比对成功(匹配); false-比对失败
+     * @throws AgentWareException 比对过程非正常结束,描述异常情况。如:参数无效、数据格式错误、未知原因等
+     */
+    boolean match(String data, String origin, Properties options) throws AgentWareException;
+
+    /**
+     * 获得生物特征类型
+     * @return 类型定义,英文名
+     */
+    String getType();
+}
+

+ 7 - 0
xms-auth/shared/agent-ware-api/src/main/java/jit/xms/auth/agent/spi/AgentWareException.java

@@ -0,0 +1,7 @@
+package jit.xms.auth.agent.spi;
+
+public class AgentWareException extends Exception {
+    public AgentWareException(String message) {
+        super(message);
+    }
+}

+ 62 - 0
xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/config/AgentMultiFactorConfigure.kt

@@ -0,0 +1,62 @@
+package jit.xms.agent.support.config
+
+import jit.xms.auth.agent.spi.AgentMultiFactorProvider
+import jit.xms.auth.agent.spi.AgentWareException
+import org.springframework.boot.context.properties.ConfigurationProperties
+
+@ConfigurationProperties(prefix = "xms.agent.multi-factor")
+class AgentMultiFactorConfigure {
+
+    /**
+     * 特征数据是否加密默认配置
+     */
+    var encrypted: Boolean = false
+
+    /**
+     * Provider是否单例默认配置
+     */
+    var singleton: Boolean = true
+
+    /**
+     * 多因子Provider配置信息
+     */
+    var providers: Map<String, MultiFactorProviderProperties> = emptyMap()
+
+    /**
+     * 获得Provider的扩展参数
+     */
+    fun getProvider(key: String): AgentMultiFactorProvider {
+        val prov = providers.let { it[key] } ?: throw AgentWareException("Provider[$key] not configure")
+        // 处理默认值
+        if (prov.encrypted == null) prov.encrypted = encrypted
+        if (prov.singleton == null) prov.singleton = singleton
+
+        // 检查类路径
+        if(!prov.providerClassIsLoadable()) {
+            throw AgentWareException("Can't load Multi-factor Provider class: ${prov.providerClassName}")
+        }
+        return prov.getBean()
+    }
+
+    /**
+     * 获得Provider的扩展参数
+     */
+    fun getProvExtra(key: String): Map<String, Any?> {
+        return providers[key]?.extra ?: emptyMap()
+    }
+
+    /**
+     * 获得Provider的加密配置
+     */
+    fun isProvEncrypted(key: String): Boolean {
+        return providers[key]?.encrypted ?: encrypted
+    }
+
+    /**
+     * 获得Provider的是否单例配置
+     */
+    fun isProvSingleton(key: String): Boolean {
+        return providers[key]?.singleton ?: singleton
+    }
+
+}

+ 77 - 0
xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/config/MultiFactorProviderProperties.kt

@@ -0,0 +1,77 @@
+package jit.xms.agent.support.config
+
+import jit.xms.auth.agent.spi.AgentMultiFactorProvider
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.util.ClassUtils
+import org.springframework.validation.annotation.Validated
+import java.util.*
+import javax.validation.constraints.NotNull
+
+@Validated
+class MultiFactorProviderProperties {
+
+    /**
+     * provider显示名称
+     */
+    @NotNull
+    var name: String? = null
+    /**
+     * 特征数据是否加密
+     */
+    var encrypted: Boolean? = null
+    /**
+     * 是否单例
+     */
+    var singleton: Boolean? = null
+    /**
+     * Fully qualified name of the multi-factor spi provider.
+     */
+    @NotNull
+    var providerClassName: String? = null
+    /**
+     * extra properties for provider instance.
+     */
+    var extra: Map<String, Any?>? = null
+
+    fun providerClassIsLoadable(): Boolean {
+        return try {
+            ClassUtils.forName(providerClassName!!, null)
+            true
+        } catch (ex: UnsupportedClassVersionError) { // Driver library has been compiled with a later JDK, propagate error
+            throw ex
+        } catch (ex: Throwable) {
+            log.warn(ex.message, ex)
+            false
+        }
+    }
+
+    @Synchronized
+    fun getBean(): AgentMultiFactorProvider {
+        if(singleton != false) {
+            return instance ?: createInstance().also {
+                instance = it
+            }
+        }
+        return createInstance()
+    }
+
+    private var instance: AgentMultiFactorProvider? = null
+    private fun createInstance(): AgentMultiFactorProvider {
+        // 创建Provider实例
+        val provider: AgentMultiFactorProvider = ClassUtils.forName(
+                providerClassName!!,
+                null
+        ).getDeclaredConstructor().newInstance() as AgentMultiFactorProvider
+        return provider.also {
+            // 配置Provider参数,执行初始化
+            val config = Properties()
+            extra?.let { config.putAll(it) }
+            provider.init(config)
+        }
+    }
+
+    companion object {
+        val log: Logger = LoggerFactory.getLogger(AgentMultiFactorProvider::class.java)
+    }
+}

+ 27 - 0
xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/web/AgentControllerAdvice.kt

@@ -0,0 +1,27 @@
+package jit.xms.agent.support.web
+
+import gaf3.core.data.ErrorResult
+import gaf3.core.exception.BusinessError
+import jit.xms.auth.agent.spi.AgentWareException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpStatus
+import org.springframework.http.server.ServerHttpResponse
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseBody
+import org.springframework.web.bind.annotation.RestControllerAdvice
+
+
+@RestControllerAdvice
+class AgentControllerAdvice {
+    private val log: Logger = LoggerFactory.getLogger(AgentControllerAdvice::class.java)
+
+    @ExceptionHandler(AgentWareException::class)
+    @ResponseBody
+    fun handleError(ex: AgentWareException, res: ServerHttpResponse): ErrorResult {
+        log.warn("AgentWareException: {}", ex.message)
+        log.debug("handleError", ex)
+        res.setStatusCode(HttpStatus.BAD_REQUEST)
+        return ErrorResult(BusinessError.ERR_SERVICE_FAULT, "认证代理错误", ex.message)
+    }
+}

+ 23 - 0
xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/web/AgentWebAdviceConfiguration.kt

@@ -0,0 +1,23 @@
+package jit.xms.agent.support.web
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import jit.xms.agent.support.web.reactive.AgentControllerAdvice as AgentReactiveControllerAdvice
+
+@Configuration
+open class AgentWebAdviceConfiguration {
+
+    @Bean
+    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+    open fun multiFactorControllerAdvice(): AgentControllerAdvice {
+        return AgentControllerAdvice()
+    }
+
+    @Bean
+    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
+    open fun multiFactorReactiveControllerAdvice(): AgentReactiveControllerAdvice {
+        return AgentReactiveControllerAdvice()
+    }
+}
+

+ 27 - 0
xms-auth/shared/agent-ware-api/src/main/kotlin/jit/xms/agent/support/web/reactive/AgentControllerAdvice.kt

@@ -0,0 +1,27 @@
+package jit.xms.agent.support.web.reactive
+
+import gaf3.core.data.ErrorResult
+import gaf3.core.exception.BusinessError
+import jit.xms.auth.agent.spi.AgentWareException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpStatus
+import org.springframework.http.server.reactive.ServerHttpResponse
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseBody
+import org.springframework.web.bind.annotation.RestControllerAdvice
+
+
+@RestControllerAdvice
+class AgentControllerAdvice {
+    private val log: Logger = LoggerFactory.getLogger(AgentControllerAdvice::class.java)
+
+    @ExceptionHandler(AgentWareException::class)
+    @ResponseBody
+    fun handleError(ex: AgentWareException, res: ServerHttpResponse): ErrorResult {
+        log.warn("AgentWareException: {}", ex.message)
+        log.debug("handleError", ex)
+        res.statusCode = HttpStatus.BAD_REQUEST
+        return ErrorResult(BusinessError.ERR_SERVICE_FAULT, "认证代理错误", ex.message)
+    }
+}

+ 40 - 0
xms-auth/shared/multi-factor-api/build.gradle.kts

@@ -0,0 +1,40 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * This is a general purpose Gradle build.
+ * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
+ */
+
+group = "jit.xms"
+version = "1.0.0"
+
+val mavenUser: String? by project
+val mavenPassword: String? by project
+
+plugins {
+    id("java-library")
+    id("maven-publish")
+    kotlin("jvm")
+}
+
+dependencies {
+//    implementation(fileTree("$rootDir/libs") { include("gaf-core-shared-*.jar") })
+    implementation("cc-lotus.gaf3:gaf-core-shared:${property("gafVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter:${property("springBootVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter-validation:${property("springBootVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter-webflux:${property("springBootVersion")}")
+    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:${property("springBootVersion")}")
+}
+
+tasks.named<Jar>("jar") {
+    archiveBaseName.set("xms-${project.name}")
+}
+
+publishing {
+    publications {
+        create<MavenPublication>("maven") {
+            artifactId = "xms-${project.name}"
+            from(components["java"])
+        }
+    }
+}

+ 7 - 0
xms-auth/shared/multi-factor-api/src/main/java/jit/xms/auth/api/MultiFactorException.java

@@ -0,0 +1,7 @@
+package jit.xms.auth.api;
+
+public class MultiFactorException extends Exception {
+    public MultiFactorException(String message) {
+        super(message);
+    }
+}

+ 42 - 0
xms-auth/shared/multi-factor-api/src/main/java/jit/xms/auth/api/spi/MultiFactorProvider.java

@@ -0,0 +1,42 @@
+package jit.xms.auth.api.spi;
+
+import jit.xms.auth.api.MultiFactorException;
+
+import java.util.Properties;
+
+public interface MultiFactorProvider {
+    /**
+     * 接口初始化(可选)
+     * 系统启动后,首次调用比对接口前调用,调用一次即可
+     * @param config 配置对象
+     * @throws MultiFactorException 初始化失败原因
+     */
+    void init(Properties config) throws MultiFactorException;
+
+    /**
+     * 一对一生物特征比对
+     * @param data 生物特征数据
+     * @param origin 原始采集数据
+     * @param options 扩展参数(可选)
+     * @return 比对过程正常结束,返回生物特征比对结果,true-比对成功(匹配); false-比对失败
+     * @throws MultiFactorException 比对过程非正常结束,描述异常情况。如:参数无效、数据格式错误、未知原因等
+     */
+    boolean auth(String data, String origin, Properties options) throws MultiFactorException;
+
+    /**
+     * 一对多生物特征比对
+     * @param data 生物特征数据
+     * @param origin 原始采集数据接口
+     * @param options 扩展参数(可选)
+     * @return 比对过程正常结束,返回生物特征比对结果,true-比对成功(匹配); false-比对失败
+     * @throws MultiFactorException 比对过程非正常结束,描述异常情况。如:参数无效、数据格式错误、未知原因等
+     */
+    boolean authN(String data, String[] origin, Properties options) throws MultiFactorException;
+
+    /**
+     * 获得生物特征类型
+     * @return 类型定义,英文名
+     */
+    String getType();
+}
+

+ 6 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AppInfo.kt

@@ -0,0 +1,6 @@
+package jit.xms.auth.api.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class AppInfo(val appId: String, val appName: String)

+ 6 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AuthInfo.kt

@@ -0,0 +1,6 @@
+package jit.xms.auth.api.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class AuthInfo(val userId: String, val name: String, val cred: String, val account: String? = null, val appId: String? = null)

+ 6 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AuthTicket.kt

@@ -0,0 +1,6 @@
+package jit.xms.auth.api.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class AuthTicket(val ticket: String, val loginTypes: Array<String>? = null, val userInfo: UserInfo? = null, val appInfos: Array<AppInfo>? = null)

+ 6 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/AuthToken.kt

@@ -0,0 +1,6 @@
+package jit.xms.auth.api.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class AuthToken(val info: AuthInfo, val token: String)

+ 34 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/domain/UserInfo.kt

@@ -0,0 +1,34 @@
+package jit.xms.auth.api.domain
+
+import com.fasterxml.jackson.annotation.JsonInclude
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class UserInfo (
+
+    var userId: String? = null,
+
+    /**
+     * 姓名
+     */
+    var name: String? = null,
+    /**
+     * 性别
+     */
+    var sex: String? = null,
+    /**
+     * 职位、称谓
+     */
+    var title: String? = null,
+    /**
+     * 身份证号
+     */
+    var sfzh: String? = null,
+    /**
+     * 保障卡号
+     */
+    var bzkh: String? = null,
+    /**
+     * 军人证件号
+     */
+    var jrzjh: String? = null
+)

+ 86 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/config/MultiFactorConfigure.kt

@@ -0,0 +1,86 @@
+package jit.xms.auth.api.support.config
+
+import jit.xms.auth.api.MultiFactorException
+import jit.xms.auth.api.spi.MultiFactorProvider
+import org.springframework.boot.context.properties.ConfigurationProperties
+
+@ConfigurationProperties(prefix = "xms.auth.multi-factor")
+class MultiFactorConfigure {
+
+    /**
+     * 特征数据是否加密默认配置
+     */
+    var encrypted: Boolean = false
+    /**
+     * 数据库中特征数据是否加密
+     */
+    var encryptedDb: Boolean = false
+
+    /**
+     * Provider是否单例默认配置
+     */
+    var singleton: Boolean = true
+
+    /**
+     * 认证服务是否支持一对多模式,以及Provider是否支持一对多比对默认配置
+     */
+    var oneToMany: Boolean = true
+
+    /**
+     * 多因子Provider配置信息
+     */
+    var providers: Map<String, ProviderProperties> = emptyMap()
+
+    /**
+     * 获得Provider的扩展参数
+     */
+    fun getProvider(key: String): MultiFactorProvider {
+        val prov = providers.let { it[key] } ?: throw MultiFactorException("Provider[$key] not configure")
+        // 处理默认值
+        if (prov.encrypted == null) prov.encrypted = encrypted
+        if (prov.encryptedDb == null) prov.encryptedDb = encryptedDb
+        if (prov.singleton == null) prov.singleton = singleton
+        if (prov.oneToMany == null) prov.oneToMany = oneToMany
+
+        // 检查类路径
+        if(!prov.providerClassIsLoadable()) {
+            throw MultiFactorException("Can't load Multi-factor Provider class: ${prov.providerClassName}")
+        }
+        return prov.getBean()
+    }
+
+    /**
+     * 获得Provider的扩展参数
+     */
+    fun getProvExtra(key: String): Map<String, Any?> {
+        return providers[key]?.extra ?: emptyMap()
+    }
+
+    /**
+     * 获得Provider的加密配置
+     */
+    fun isProvEncrypted(key: String): Boolean {
+        return providers[key]?.encrypted ?: encrypted
+    }
+
+    /**
+     * 获得Provider的加密配置
+     */
+    fun isProvEncryptedDb(key: String): Boolean {
+        return providers[key]?.encryptedDb ?: encryptedDb
+    }
+
+    /**
+     * 获得Provider的是否单例配置
+     */
+    fun isProvSingleton(key: String): Boolean {
+        return providers[key]?.singleton ?: singleton
+    }
+
+    /**
+     * 获得Provider的是否支持一对多配置
+     */
+    fun isProvOneToMany(key: String): Boolean {
+        return providers[key]?.oneToMany ?: oneToMany
+    }
+}

+ 85 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/config/ProviderProperties.kt

@@ -0,0 +1,85 @@
+package jit.xms.auth.api.support.config
+
+import jit.xms.auth.api.spi.MultiFactorProvider
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.util.ClassUtils
+import org.springframework.validation.annotation.Validated
+import java.util.*
+import javax.validation.constraints.NotNull
+
+@Validated
+class ProviderProperties {
+
+    /**
+     * provider显示名称
+     */
+    @NotNull
+    var name: String? = null
+    /**
+     * 采集特征数据是否加密
+     */
+    var encrypted: Boolean? = null
+    /**
+     * 数据库中特征数据是否加密
+     */
+    var encryptedDb: Boolean? = null
+    /**
+     * 是否单例
+     */
+    var singleton: Boolean? = null
+    /**
+     * 是否支持一对多比对
+     */
+    var oneToMany: Boolean? = null
+    /**
+     * Fully qualified name of the multi-factor spi provider.
+     */
+    @NotNull
+    var providerClassName: String? = null
+    /**
+     * extra properties for provider instance.
+     */
+    var extra: Map<String, Any?>? = null
+
+    fun providerClassIsLoadable(): Boolean {
+        return try {
+            ClassUtils.forName(providerClassName!!, null)
+            true
+        } catch (ex: UnsupportedClassVersionError) { // Driver library has been compiled with a later JDK, propagate error
+            throw ex
+        } catch (ex: Throwable) {
+            log.warn(ex.message, ex)
+            false
+        }
+    }
+
+    @Synchronized
+    fun getBean(): MultiFactorProvider {
+        if(singleton != false) {
+            return instance ?: createInstance().also {
+                instance = it
+            }
+        }
+        return createInstance()
+    }
+
+    private var instance: MultiFactorProvider? = null
+    private fun createInstance(): MultiFactorProvider {
+        // 创建Provider实例
+        val provider: MultiFactorProvider = ClassUtils.forName(
+                providerClassName!!,
+                null
+        ).getDeclaredConstructor().newInstance() as MultiFactorProvider
+        return provider.also {
+            // 配置Provider参数,执行初始化
+            val config = Properties()
+            extra?.let { config.putAll(it) }
+            provider.init(config)
+        }
+    }
+
+    companion object {
+        val log: Logger = LoggerFactory.getLogger(MultiFactorProvider::class.java)
+    }
+}

+ 27 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/web/MultiFactorControllerAdvice.kt

@@ -0,0 +1,27 @@
+package jit.xms.auth.api.support.web
+
+import gaf3.core.data.ErrorResult
+import gaf3.core.exception.BusinessError
+import jit.xms.auth.api.MultiFactorException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpStatus
+import org.springframework.http.server.ServerHttpResponse
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseBody
+import org.springframework.web.bind.annotation.RestControllerAdvice
+
+
+@RestControllerAdvice
+class MultiFactorControllerAdvice {
+    private val log: Logger = LoggerFactory.getLogger(MultiFactorControllerAdvice::class.java)
+
+    @ExceptionHandler(MultiFactorException::class)
+    @ResponseBody
+    fun handleError(ex: MultiFactorException, res: ServerHttpResponse): ErrorResult {
+        log.warn("MultiFactorException: {}", ex.message)
+        log.debug("handleError", ex)
+        res.setStatusCode(HttpStatus.BAD_REQUEST)
+        return ErrorResult(BusinessError.ERR_SERVICE_FAULT, "多因子认证错误", ex.message)
+    }
+}

+ 23 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/web/MultiFactorWebAdviceConfiguration.kt

@@ -0,0 +1,23 @@
+package jit.xms.auth.api.support.web
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import jit.xms.auth.api.support.web.reactive.MultiFactorControllerAdvice as MultiFactorReactiveControllerAdvice
+
+@Configuration
+open class MultiFactorWebAdviceConfiguration {
+
+    @Bean
+    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+    open fun multiFactorControllerAdvice(): MultiFactorControllerAdvice {
+        return MultiFactorControllerAdvice()
+    }
+
+    @Bean
+    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
+    open fun multiFactorReactiveControllerAdvice(): MultiFactorReactiveControllerAdvice {
+        return MultiFactorReactiveControllerAdvice()
+    }
+}
+

+ 27 - 0
xms-auth/shared/multi-factor-api/src/main/kotlin/jit/xms/auth/api/support/web/reactive/MultiFactorControllerAdvice.kt

@@ -0,0 +1,27 @@
+package jit.xms.auth.api.support.web.reactive
+
+import gaf3.core.data.ErrorResult
+import gaf3.core.exception.BusinessError
+import jit.xms.auth.api.MultiFactorException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpStatus
+import org.springframework.http.server.reactive.ServerHttpResponse
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseBody
+import org.springframework.web.bind.annotation.RestControllerAdvice
+
+
+@RestControllerAdvice
+class MultiFactorControllerAdvice {
+    private val log: Logger = LoggerFactory.getLogger(MultiFactorControllerAdvice::class.java)
+
+    @ExceptionHandler(MultiFactorException::class)
+    @ResponseBody
+    fun handleError(ex: MultiFactorException, res: ServerHttpResponse): ErrorResult {
+        log.warn("MultiFactorException: {}", ex.message)
+        log.debug("handleError", ex)
+        res.statusCode = HttpStatus.BAD_REQUEST
+        return ErrorResult(BusinessError.ERR_SERVICE_FAULT, "多因子认证错误", ex.message)
+    }
+}

+ 41 - 0
xms-auth/shared/util/build.gradle.kts

@@ -0,0 +1,41 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * This is a general purpose Gradle build.
+ * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
+ */
+
+group = "jit.xms"
+version = "1.0.0"
+
+val mavenUser: String? by project
+val mavenPassword: String? by project
+
+plugins {
+    id("java-library")
+    id("maven-publish")
+    kotlin("jvm")
+}
+
+dependencies {
+//    implementation(fileTree("$rootDir/libs") { include("gaf-core-shared-*.jar") })
+    implementation("cc-lotus.gaf3:gaf-core-shared:${property("gafVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter:${property("springBootVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter-validation:${property("springBootVersion")}")
+    implementation("org.springframework.boot:spring-boot-starter-webflux:${property("springBootVersion")}")
+    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5")
+    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:${property("springBootVersion")}")
+}
+
+tasks.named<Jar>("jar") {
+    archiveBaseName.set("xms-auth-${project.name}")
+}
+
+publishing {
+    publications {
+        create<MavenPublication>("maven") {
+            artifactId = "xms-auth-${project.name}"
+            from(components["java"])
+        }
+    }
+}

+ 48 - 0
xms-auth/shared/util/src/main/kotlin/jit/xms/auth/util/support/config/AuthGatewayConfigure.kt

@@ -0,0 +1,48 @@
+package jit.xms.auth.util.support.config
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+import java.net.URL
+
+@ConfigurationProperties(prefix = "xms.auth.gateway")
+class AuthGatewayConfigure {
+    /**
+     * 网关服务器是否可用
+     */
+    var enabled: Boolean = false
+
+    /**
+     * 网关服务器IP地址
+     */
+    var host: String = "127.0.0.1"
+
+    /**
+     * 网关服务端口
+     */
+    var port: Int = 0
+
+    /**
+     * 应用标识,用于做访问控制
+     */
+    var appFlag: String = ""
+
+    /**
+     * 从DN中获取用户ID的正则表达式
+     * 如:"CN=([^,]*),?"
+     */
+    var extractor: String = "CN=[^,]*([^,]{18}),?"
+
+    /**
+     * 连接超时时间(秒)
+     */
+    var connectTimeout: Int = 30
+
+    /**
+     * 读超时时间(秒)
+     */
+    var soTimeout: Int = 60
+
+    /**
+     * 解密服务算法标识
+     */
+    var decryptAlgo: String = "122"
+}

+ 166 - 0
xms-auth/shared/util/src/main/kotlin/jit/xms/auth/util/support/proxy/AuthGatewayService.kt

@@ -0,0 +1,166 @@
+package jit.xms.auth.util.support.proxy
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.SerializationFeature
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import gaf3.core.exception.BusinessError
+import jit.xms.auth.util.support.config.AuthGatewayConfigure
+import org.slf4j.LoggerFactory
+
+class AuthGatewayService(private val config: AuthGatewayConfigure) {
+    val enabled: Boolean get() = config.enabled
+    fun send(req: Map<String, String?>): Map<String, String?> {
+        if (!config.enabled) throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "认证网关不可用")
+
+        val res: String = runCatching {
+            val client = SocketClient(
+                config.host,
+                config.port,
+                soTimeout = config.soTimeout,
+                connectTimeout = config.connectTimeout
+            )
+            val json = mapper.writeValueAsString(req)
+            log.debug("【密码服务】send: {}", json)
+            val result = client.process(json)
+            log.debug("【密码服务】recv: {}", result)
+            result
+        }.getOrElse {
+            log.debug("请求密码服务失败", it)
+            throw BusinessError(BusinessError.ERR_NETWORK, "请求密码服务失败")
+        }
+        return runCatching {
+            mapper.readValue(res, mutableMapOf<String, String?>()::class.java)
+        }.getOrElse {
+            log.debug("解析加密服务结果失败: {}", res, it)
+            throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析加密服务结果失败")
+        }
+    }
+
+    /**
+     * 私钥转换
+     * @param eckSym 保护证书私钥的对称密钥(ECK)
+     * @param prvKey 加密保护的证书私钥
+     * @param kekPub 新的密钥保护公钥
+     */
+    fun reWrapPriKey(eckSym: String, prvKey: String, kekPub: String): String {
+        val req = mutableMapOf(
+            "msg" to "ReWrapPriKey",
+            "pubkey" to eckSym.replace(Regex("[\r\n]"), ""),
+            "prikey" to prvKey.replace(Regex("[\r\n]"), ""),
+            "keybuf" to kekPub.replace(Regex("[\r\n]"), "")
+        )
+        val res = send(req)
+        if (res["rv"] != "0") throw BusinessError(BusinessError.ERR_SERVICE_FAULT, """密钥转换失败, 错误码: ${res["rv"]}""")
+        return res["prikey"] ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "密钥转换失败:返回结果中prikey为空")
+    }
+
+    /**
+     * 加密
+     * algo:448算法4/5/6 1102算法0x79/0x7a/0x7b
+     */
+    fun encrypt(srcData: String, pubkey: String): String {
+        val req = mutableMapOf(
+            "msg" to "encrypt",
+            "algo" to config.decryptAlgo,
+            "pubkey" to pubkey,
+            "src" to srcData
+        )
+        val res = send(req)
+        if (res["rv"] != "0") throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析加密服务结果失败")
+        return res["encryptdata"] ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "加密数据失败:返回结果中encryptdata为空")
+    }
+
+    /**
+     * 解密
+     * algo:448算法4/5/6 1102算法0x79/0x7a/0x7b
+     */
+    fun decrypt(encData: String): String {
+        val req = mutableMapOf(
+            "msg" to "decryptex",
+            "algo" to config.decryptAlgo,
+            "encdata" to encData.replace(Regex("[\r\n]"), "")
+        )
+        val res = send(req)
+        if (res["rv"] != "0") throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析解密服务结果失败")
+        return res["srcdata"] ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解密数据失败:返回结果中srcdata为空")
+    }
+
+    /**
+     * 读取证书
+     * 签名证书certtype:1/2/3,加密证书certtype:4/5/6
+     */
+    fun readCert(): String {
+        val req = mutableMapOf(
+            "msg" to "read_cert",
+            "certtype" to "5"
+        )
+        val res = send(req)
+        if (res["rv"] != "0") throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析加密服务结果失败")
+        return res["certdata"] ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "导出服务证书失败:返回结果中certdata为空")
+    }
+
+    /**
+     * 签名
+     * 448算法:0x76/0x77/0x78,1102算法:0xa6/0xa7/0xa8
+     */
+    fun sign(srcData: String): String {
+        val req = mutableMapOf(
+            "msg" to "sign",
+            "algo" to config.decryptAlgo,
+            "src" to srcData
+        )
+        val res = send(req)
+        if (res["rv"] != "0") throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析签名服务结果失败")
+        return res["signdata"] ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "签名数据失败:返回结果中signdata为空")
+    }
+
+    /**
+     * 验签
+     */
+    fun verifySign(cert: String, verifySrc: String, verifySign: String): Boolean {
+        val req = mutableMapOf(
+            "msg" to "verifybycert",
+            "cert" to cert,
+            "verifysrc" to verifySrc,
+            "verifysign" to verifySign
+        )
+        val res = send(req)
+        log.debug("验签服务返回rv=${res["rv"]}")
+        return res["rv"] == "0"
+    }
+
+    /**
+     * 获取随机数
+     */
+    fun random(long: Int): String {
+        val req = mutableMapOf(
+            "msg" to "generateradom",
+            "randomlen" to long.toString()
+        )
+        val res = send(req)
+        if (res["rv"] != "0") throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析获取随机数服务结果失败")
+        return res["randomdata"] ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "随机数数据失败:返回结果中randomdata为空")
+    }
+
+    /**
+     * 获取摘要
+     */
+    fun digest(srcData: String): String {
+        val req = mutableMapOf(
+            "msg" to "digest",
+            "algo" to config.decryptAlgo,
+            "src" to srcData
+        )
+        val res = send(req)
+        if (res["rv"] != "0") throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "解析摘要服务结果失败")
+        return res["digestdata"] ?: throw BusinessError(BusinessError.ERR_SERVICE_FAULT, "摘要数据失败:返回结果中digestdata为空")
+    }
+
+    companion object {
+        val log = LoggerFactory.getLogger(AuthGatewayService::class.java)!!
+        private val mapper = ObjectMapper().apply {
+            disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
+            registerModule(JavaTimeModule())
+        }
+    }
+}

+ 0 - 0
xms-auth/shared/util/src/main/kotlin/jit/xms/auth/util/support/proxy/SocketClient.kt


Some files were not shown because too many files changed in this diff