【java】java引入google验证

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 {


    /**
     * 时间前后偏移量
     * 用于防止客户端时间不精确导致生成的TOTP与服务器端的TOTP一直不一致
     * 如果为0,当前时间为 10:10:15
     * 则表明在 10:10:00-10:10:30 之间生成的TOTP 能校验通过
     * 如果为1,则表明在
     * 10:09:30-10:10:00
     * 10:10:00-10:10:30
     * 10:10:30-10:11:00 之间生成的TOTP 能校验通过
     * 以此类推
     */
    private var WINDOW_SIZE = 0

    /**
     * 加密方式,HmacSHA1、HmacSHA256、HmacSHA512
     */
    private var CRYPTO = "HmacSHA1"

    /**
     * 生成密钥,每个用户独享一份密钥
     *
     * @return
     */
    fun getSecretKey(): String {
        val random = SecureRandom()
        val bytes = ByteArray(20)
        random.nextBytes(bytes)
        val base32 = Base32()
        val secretKey = base32.encodeToString(bytes)
        // make the secret key more human-readable by lower-casing and
        // inserting spaces between each group of 4 characters
        return secretKey.toUpperCase()
    }

    /**
     * 生成二维码内容
     *
     * @param secretKey 密钥
     * @param account   账户名
     * @param issuer    网站地址(可不写)
     * @return
     */
    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)
        }
    }

    /**
     * 获取验证码
     *
     * @param secretKey
     * @return
     */
    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)
    }

    /**
     * 检验 code 是否正确
     *
     * @param secret 密钥
     * @param code   code
     * @param time   时间戳
     * @return
     */
    fun checkCode(secret: String, code: Long, time: Long): Boolean {
        val codec = Base32()
        val decodedKey = codec.decode(secret)
        // convert unix msec time into a 30 second "window"
        // this is per the TOTP spec (see the RFC for details)
        var t = time / 1000L / 30L
        // Window is used to check codes generated in the near past.
        // You can use this value to tune how far you're willing to go.
        var hash: Long
        for (i in -WINDOW_SIZE..WINDOW_SIZE) {
            try {
                hash = verifyCode(decodedKey, t + i)
            } catch (e: Exception) {
                // Yes, this is bad form - but
                // the exceptions thrown would be rare and a static
                // configuration problem
                // e.printStackTrace();
                throw RuntimeException(e.message)
            }
            if (hash == code) {
                return true
            }
        }
        return false
    }

    /**
     * 根据时间偏移量计算
     *
     * @param key
     * @param t
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    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()
        // We're using a long because Java hasn't got unsigned int.
        var truncatedHash = 0L
        for (i in 0..3) {
            truncatedHash = truncatedHash shl 8
            // We are dealing with signed bytes:
            // we just keep the first byte.
            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 {
        // Adding one byte to get the right conversion
        // Values starting with "0" can be converted
        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
            }
            // Gather high nibble
            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")
                }
            }
            // Shift high nibble four bits to the left
            val shiftedNibble = nibble shl 4
            // Get next char from string
            val nextChar = hex[i + 1]
            // Gather low nibble
            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")
                }
            }
            // Combine high nibble with low nibble
            val b = shiftedNibble or nextNibble
            // Store byte in array
            bArray[j++] = b.toByte()
        }
        // Remove trailing zeros
        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业务代码实现

/**
 3. 绑定谷歌验证
 */
@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")!!)
    }

}

/**
 * 验证 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());
}


/**
 * 验证 code 是否正确
 */
@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")!!)
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值