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 = 0
private 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 / 30
val 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 / 30L
var hash: Long
for (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 = t
for (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 = 0L
for (i in 0..3) {
truncatedHash = truncatedHash shl 8
truncatedHash = truncatedHash or (hash[offset + i].toInt() and 0xFF).toLong()
}
truncatedHash = truncatedHash and 0x7FFFFFFF
truncatedHash %= 1000000
return truncatedHash
}
private fun generateTOTP(
key: String,
time: String?,
returnDigits: String,
crypto: String
): String {
var time = time
var code = ""
val tm = time!!.toLong(16)
val hash = hmacSha(crypto, hexStr2Bytes(key), longToByteArray(tm))
var offset = hash[hash.size - 1] and 0xf
val 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? = null
try {
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 = 0
for (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 4
val 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 nextNibble
bArray[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 = encryptedMessage
user.bindGoogleAuth=true
true
} 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 = null
user.bindGoogleAuth=false
true
}else{
throw ProgramException(i18nUtils.getMessage("INVALID_GOOGLE_VERIFICATION_CODE")!!)
}
}