|
@@ -0,0 +1,130 @@
|
|
|
+package jit.xms.qrscan.service
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON
|
|
|
+import gaf3.core.exception.BusinessError
|
|
|
+import gaf3.core.exception.BusinessError.ERR_BUSINESS
|
|
|
+import gaf3.core.services.auth.service.GafAuthService
|
|
|
+import io.jsonwebtoken.Claims
|
|
|
+import io.jsonwebtoken.ExpiredJwtException
|
|
|
+import io.jsonwebtoken.Jwts
|
|
|
+import io.jsonwebtoken.SignatureAlgorithm
|
|
|
+import jit.xms.auth.api.domain.AuthInfo
|
|
|
+import jit.xms.auth.api.support.config.MultiFactorConfigure
|
|
|
+import jit.xms.core.services.agent.domain.AuthToken
|
|
|
+import jit.xms.core.services.app.infos.service.AppInfoService
|
|
|
+import jit.xms.core.services.bff.service.BffAcctRoleService
|
|
|
+import jit.xms.core.services.user.accts.service.UserAcctService
|
|
|
+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 jit.xms.qrscan.QRScanAuthConfigure
|
|
|
+import jit.xms.qrscan.domain.AuthForm
|
|
|
+import jit.xms.qrscan.domain.TicketResult
|
|
|
+import jit.xms.qrscan.support.WebSocketSender
|
|
|
+import org.springframework.beans.factory.annotation.Autowired
|
|
|
+import org.springframework.beans.factory.annotation.Qualifier
|
|
|
+import org.springframework.beans.factory.annotation.Value
|
|
|
+import org.springframework.stereotype.Service
|
|
|
+import java.security.Key
|
|
|
+import java.time.Instant
|
|
|
+import java.util.*
|
|
|
+import java.util.concurrent.ConcurrentHashMap
|
|
|
+import javax.crypto.spec.SecretKeySpec
|
|
|
+
|
|
|
+@Service
|
|
|
+class AuthService(@Qualifier("jwtSigningKey") val jwtSigningKey: Key, val authConfig: QRScanAuthConfigure) {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ lateinit var senderMap: ConcurrentHashMap<String, WebSocketSender>
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ lateinit var multiFactorConfigure: MultiFactorConfigure
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ lateinit var gafAuth: GafAuthService
|
|
|
+
|
|
|
+ @Value("\${jwt.secret}")
|
|
|
+ lateinit var gafSecretKey: String
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成Ticket并发送WebSocketMessage
|
|
|
+ */
|
|
|
+ fun ticket(id: String, userId: String, token: String) {
|
|
|
+ val webSocketSender = senderMap[id] ?: throw BusinessError(ERR_BUSINESS, "该二维码所属客户端不存在")
|
|
|
+ val realToken = token.removePrefix("Bearer ")
|
|
|
+ val gafKeyBytes = gafSecretKey.toByteArray().copyOf(32)
|
|
|
+ val gafKey = SecretKeySpec(gafKeyBytes, SignatureAlgorithm.HS256.jcaName)
|
|
|
+ val claims = parseJwt(realToken, gafKey)
|
|
|
+ val authResult = TicketResult(0, "认证成功", ticket = createTicket(claims))
|
|
|
+ webSocketSender.send(JSON.toJSONString(authResult))
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对本服务签发的Ticket进行认证
|
|
|
+ */
|
|
|
+ fun auth(form: AuthForm): AuthToken {
|
|
|
+ if (form.type.isNullOrEmpty() || !form.type.equals("qrcode", ignoreCase = true)) throw BusinessError(ERR_BUSINESS, "凭证类型不合法")
|
|
|
+ if (form.data.isNullOrEmpty()) throw BusinessError(ERR_BUSINESS, "凭证数据不合法")
|
|
|
+ val provider = runCatching {
|
|
|
+ multiFactorConfigure.getProvider(form.type!!)
|
|
|
+ }.getOrElse {
|
|
|
+ throw BusinessError(ERR_PROV_NOT_SUPPORT, "凭证类型不支持")
|
|
|
+ }
|
|
|
+ val result = kotlin.runCatching {
|
|
|
+ provider.auth(null, form.data, null)
|
|
|
+ }.getOrElse {
|
|
|
+ throw BusinessError(ERR_CRED_NOT_MATCH, "认证接口调用失败")
|
|
|
+ }
|
|
|
+ if (!result) throw BusinessError(ERR_CRED_NOT_MATCH, "凭证数据校验失败")
|
|
|
+ // 解析ticket数据
|
|
|
+ val parse = parseJwt(form.data!!, jwtSigningKey)
|
|
|
+ val userId = parse["userId"] as String? ?: throw BusinessError("解析Ticket中用户信息为空")
|
|
|
+ val roles = parse["roles"] as ArrayList<String>
|
|
|
+ val user = XmsUserSimple().apply {
|
|
|
+ this.userId = userId
|
|
|
+ name = parse["name"] as String? ?: "用户"
|
|
|
+ }
|
|
|
+ val gafToken = gafAuth.createJwt(subject = "admin",
|
|
|
+ userId = user.userId!!,
|
|
|
+ name = user.name!!,
|
|
|
+ roles = roles.toTypedArray())
|
|
|
+ return AuthToken(userinfo = user, token = gafToken, roles = roles.toTypedArray())
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建Ticket
|
|
|
+ */
|
|
|
+ fun createTicket(claims: Claims): String {
|
|
|
+ return Jwts.builder()
|
|
|
+ .setSubject("qrscan")
|
|
|
+ .setIssuer(authConfig.jwtIssuer)
|
|
|
+ .setExpiration(Date.from(Instant.now().plusSeconds(authConfig.jwtValiditySec)))
|
|
|
+ .claim("userId", claims["userId"] as String?)
|
|
|
+ .claim("name", claims["name"] as String?)
|
|
|
+ .claim("roles", claims["roles"] as ArrayList<String>)
|
|
|
+ .signWith(jwtSigningKey)
|
|
|
+ .compact()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析Jwt
|
|
|
+ */
|
|
|
+ fun parseJwt(jwt: String, key: Key): Claims {
|
|
|
+ try {
|
|
|
+ val jws = Jwts.parser().setSigningKey(key).parseClaimsJws(jwt)
|
|
|
+ return jws.body
|
|
|
+ } catch (ex: ExpiredJwtException) {
|
|
|
+ throw BusinessError(ERR_TICKET_EXPIRED, "Ticket已过期")
|
|
|
+ } catch (ex: Throwable) {
|
|
|
+ throw BusinessError(ERR_TICKET_INVALID, "Ticket无效")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ companion object {
|
|
|
+ const val ERR_CRED_NOT_MATCH = ERR_BUSINESS - 1
|
|
|
+ const val ERR_TICKET_EXPIRED = ERR_BUSINESS - 6
|
|
|
+ const val ERR_TICKET_INVALID = ERR_BUSINESS - 7
|
|
|
+ const val ERR_PROV_NOT_SUPPORT = ERR_BUSINESS - 8
|
|
|
+ }
|
|
|
+}
|