1. maven引入
<dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.0</version></dependency>
2. google 工具类
import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.binary.Hex
import org.springframework.stereotype.Component
import java.io.UnsupportedEncodingException
import java.net.URLEncoder
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.and@Component
class GoogleAuthenticatorUtil {private var WINDOW_SIZE = 0private var CRYPTO = "HmacSHA1"fun getSecretKey(): String {val random = SecureRandom()val bytes = ByteArray(20)random.nextBytes(bytes)val base32 = Base32()val secretKey = base32.encodeToString(bytes)return secretKey.toUpperCase()}fun getQrCodeText(secretKey: String, account: String, issuer: String): String {val normalizedBase32Key = secretKey.replace(" ", "").toUpperCase()return try {"otpauth://totp/" + URLEncoder.encode((if (issuer.isNotEmpty()) "$issuer:" else "") + account, "UTF-8").replace("+", "%20") +"?secret=" + URLEncoder.encode(normalizedBase32Key, "UTF-8").replace("+", "%20") +if (issuer.isNotEmpty()) "&issuer=" + URLEncoder.encode(issuer, "UTF-8").replace("+", "%20") else ""} catch (e: UnsupportedEncodingException) {throw IllegalStateException(e)}}fun getCode(secretKey: String): String {val normalizedBase32Key = secretKey.replace(" ", "").toUpperCase()val base32 = Base32()val bytes = base32.decode(normalizedBase32Key)val hexKey = Hex.encodeHexString(bytes)val time = System.currentTimeMillis() / 1000 / 30val hexTime = java.lang.Long.toHexString(time)return generateTOTP(hexKey, hexTime, "6", CRYPTO)}fun checkCode(secret: String, code: Long, time: Long): Boolean {val codec = Base32()val decodedKey = codec.decode(secret)var t = time / 1000L / 30Lvar hash: Longfor (i in -WINDOW_SIZE..WINDOW_SIZE) {try {hash = verifyCode(decodedKey, t + i)} catch (e: Exception) {throw RuntimeException(e.message)}if (hash == code) {return true}}return false}private fun verifyCode(key: ByteArray, t: Long): Long {val data = ByteArray(8)var value = tfor (i in 7 downTo 0) {data[i] = value.toByte()value = value shr 8}val signKey = SecretKeySpec(key, CRYPTO)val mac = Mac.getInstance(CRYPTO)mac.init(signKey)val hash = mac.doFinal(data)var offset = hash[20 - 1] and 0xF.toByte()var truncatedHash = 0Lfor (i in 0..3) {truncatedHash = truncatedHash shl 8truncatedHash = truncatedHash or (hash[offset + i].toInt() and 0xFF).toLong()}truncatedHash = truncatedHash and 0x7FFFFFFFtruncatedHash %= 1000000return truncatedHash}private fun generateTOTP(key: String,time: String?,returnDigits: String,crypto: String): String {var time = timevar code = ""val tm = time!!.toLong(16)val hash = hmacSha(crypto, hexStr2Bytes(key), longToByteArray(tm))var offset = hash[hash.size - 1] and 0xfval binary =((hash[offset.toInt()].toInt() and 0x7f) shl 24 or (hash[offset + 1].toInt() and 0xff) shl 16 or (hash[offset + 2].toInt() and 0xff) shl 8 or (hash[offset + 3].toInt() and 0xff)).toLong()val otp = binary % DIGITS_POWER[returnDigits.toInt()]code = otp.toString()while (code.length < returnDigits.toInt()) {code = "0$code"}return code}private fun hmacSha(crypto: String, keyBytes: ByteArray, text: ByteArray?): ByteArray {var mac: Mac? = nulltry {mac = Mac.getInstance(crypto)val secretKeySpec = SecretKeySpec(keyBytes, "RAW")mac!!.init(secretKeySpec)} catch (e: NoSuchAlgorithmException) {e.printStackTrace()} catch (e: InvalidKeyException) {e.printStackTrace()}return mac!!.doFinal(text)}private fun hexStr2Bytes(hex: String): ByteArray {val bArray = ByteArray(hex.length / 2 + 1)var j = 0for (i in 0 until hex.length) {var c = hex[i]if (c.isWhitespace()) {continue}val nibble: Int = when (c) {in '0'..'9' -> {c - '0'}in 'a'..'f' -> {c - 'a' + 10}in 'A'..'F' -> {c - 'A' + 10}else -> {throw IllegalArgumentException("Invalid hex character: $c")}}val shiftedNibble = nibble shl 4val nextChar = hex[i + 1]val nextNibble: Int = when (nextChar) {in '0'..'9' -> {nextChar - '0'}in 'a'..'f' -> {nextChar - 'a' + 10}in 'A'..'F' -> {nextChar - 'A' + 10}else -> {throw IllegalArgumentException("Invalid hex character: $nextChar")}}val b = shiftedNibble or nextNibblebArray[j++] = b.toByte()}return if (bArray.size == j) {bArray} else {bArray.copyOf(j)}}private fun longToByteArray(value: Long): ByteArray {val buffer = ByteArray(8)for (i in 7 downTo 0) {buffer[i] = (value shr 8 * (7 - i)).toByte()}return buffer}private val DIGITS_POWER = intArrayOf(1,10,100,1000,10000,100000,1000000,10000000,100000000)}
3. kotlin业务代码实现
@RestfulPack
@SaCheckLogin
@PostMapping("bingGoogleAuth")
fun bingGoogleAuth(@RequestBody bindGoogleAuthCodeDto: BindGoogleAuthCodeDto): Boolean {if (StringUtils.isNotBlank(user.googleAuth)) {throw ProgramException(i18nUtils.getMessage("UNALLOWED_OPERATIONS")!!)}return if (googleAuthenticator.checkCode(bindGoogleAuthCodeDto.secretKey,bindGoogleAuthCodeDto.code,System.currentTimeMillis())) {val encryptedMessage = TripleDesUtils.encrypt(bindGoogleAuthCodeDto.secretKey, user.salt)user.googleAuth = encryptedMessageuser.bindGoogleAuth=truetrue} else {throw ProgramException(i18nUtils.getMessage("INVALID_GOOGLE_VERIFICATION_CODE")!!)}}
@RestfulPack
@GetMapping("checkCode")
fun checkCode(@RequestParam("code") code: Long): Boolean {val user = UserUtil.get()if (StringUtils.isBlank(user.googleAuth)){throw ProgramException(i18nUtils.getMessage("UNALLOWED_OPERATIONS")!!)}return googleAuthenticator.checkCode(TripleDesUtils.decrypt(user.googleAuth!!,user.salt), code, System.currentTimeMillis());
}
@RestfulPack
@PostMapping("removeGoogleAuth")
fun removeGoogleAuth(@RequestBody bindGoogleAuthCodeDto: BindGoogleAuthCodeDto): Boolean {val user = UserUtil.get()if (StringUtils.isBlank(user.googleAuth)){throw ProgramException(i18nUtils.getMessage("UNALLOWED_OPERATIONS")!!)}return if (googleAuthenticator.checkCode(TripleDesUtils.decrypt(user.googleAuth!!,user.salt), bindGoogleAuthCodeDto.code, System.currentTimeMillis())==true){user.googleAuth = nulluser.bindGoogleAuth=falsetrue}else{throw ProgramException(i18nUtils.getMessage("INVALID_GOOGLE_VERIFICATION_CODE")!!)}
}