【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 charactersreturn 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 / 30val 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: Longfor (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 = 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()// We're using a long because Java hasn't got unsigned int.var truncatedHash = 0Lfor (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 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 {// Adding one byte to get the right conversion// Values starting with "0" can be convertedval bArray = ByteArray(hex.length / 2 + 1)var j = 0for (i in 0 until hex.length) {var c = hex[i]if (c.isWhitespace()) {continue}// Gather high nibbleval 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 leftval shiftedNibble = nibble shl 4// Get next char from stringval nextChar = hex[i + 1]// Gather low nibbleval 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 nibbleval b = shiftedNibble or nextNibble// Store byte in arraybArray[j++] = b.toByte()}// Remove trailing zerosreturn 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 = encryptedMessageuser.bindGoogleAuth=truetrue} 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 = nulluser.bindGoogleAuth=falsetrue}else{throw ProgramException(i18nUtils.getMessage("INVALID_GOOGLE_VERIFICATION_CODE")!!)}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/708978.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Neo4j 数据冷备

1、数据备份 进入图谱程序所在目录bin/下&#xff0c;执行&#xff1a; neo4j-admin dump --databasegraph.db --topath/***.dump注&#xff1a;path为备份dump文件创建路径&#xff0c;需注意文件的读写权限 2、数据恢复 将文件传输到需恢复的图谱机器上&#xff0c;执行&…

【Elasticsearch索引】Recovery恢复索引

文章目录 索引恢复恢复列表获取恢复信息响应详细信息正在进行的恢复响应解析高级设置 本地分片恢复事务日志 索引恢复 索引恢复提供了对正在进行的索引分片恢复的洞察。恢复状态可以针对特定的索引报告&#xff0c;也可以在集群范围内报告。 恢复列表 recovery命令是索引分片…

C++类模板详解

目录 1.模板的概念 2.类模板 1.类模板基本语法 2.类模板与函数模板区别 3.类模板中成员函数创建时机 4.类模板对象做函数参数 typeid&#xff08;&#xff09;.name() 5.类模板与继承 6.类模板成员函数类外实现 7.类模板分文件编写 #pragma once 8.类模板与友元 1.…

企业有了ERP,为什么还要上BI?

在我们以往和企业的沟通过程中&#xff0c;我们发现还是有相当多的一部分企业对于商业智能 BI 了解不多&#xff0c;或者对商业智能 BI 的理解仅停留在花花绿绿的可视化页面上&#xff0c;要么就是提出以下类似问题&#xff1a; 财务部门&#xff1a;BI 的财务分析指标也就是三…

【算法分析与设计】最大二叉树

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最…

linux rm -rf 报错badly placed ()‘s

在文件名或路径中包含了不当的字符而导致的 1.使用转义字符 可以在特殊字符前面加上反斜杠&#xff08;\&#xff09;进行转义&#xff0c;以避免它们被解释为特殊字符。删除包含括号的文件 rm -rf 文件\(1\).zip2.使用引号或单引号 可以将文件名或路径用引号或单引号括起来&a…

【C#】SixLabors.ImageSharp和System.Drawing两者知多少

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…

Spring注解之参数校验

目录 一些常用的字段验证的注解 验证请求体(RequestBody) 验证请求参数(Path Variables 和 Request Parameters) 数据的校验的重要性就不用说了&#xff0c;即使在前端对数据进行校验的情况下&#xff0c;我们还是要对传入后端的数据再进行一遍校验&#xff0c;避免用户绕过…

去中心化时代,品牌如何赢得确定性增长

去中心化时代下&#xff0c;品牌面临众多挑战。在如今复杂的环境下&#xff0c;有很多不确定的因素&#xff0c;流量、资本等等&#xff0c;这些都是品牌发展过程中的不确定因素&#xff0c;越是复杂的环境下&#xff0c;品牌越要保证自己核心优势&#xff0c;找到并放大我们的…

C语言数据结构基础-单链表

1.链表概念 在前面的学习中&#xff0c;我们知道了线性表&#xff0c;其中逻辑结构与物理结构都连续的叫顺序表&#xff0c;那么&#xff1a; 链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。 2.链表组…

electron无法设置自己的图标?渲染进程require报错?

electron无法设置自己的图标&#xff1f; 极有可能是图标太大&#xff0c;或者宽高不同 我推荐的网址icon转换 选着20x20一般就可以 渲染进程无法使用require?一直报错&#xff1f; webPreferences: {nodeIntegration: true, enableRemoteModule: true, contextIsolation: …

【软件设计师】多元化多方面了解多媒体技术的内容

&#x1f413; 多媒体技术基本概念 多媒体主要是指文字、声音和图像等多种表达信息的形式和媒体&#xff0c;它强调多媒体信息的综合和集成处理。多媒体技术依赖于计算机的数字化和交互处理能力&#xff0c;它的关键是信息压缩技术和光盘存储技术。 亮度 亮度是光作用于人眼时所…

GO语言学习笔记(与Java的比较学习)(二)

控制结构 if-else&#xff1a; 关键字 if 和 else 之后的左大括号 { 必须和关键字在同一行&#xff0c;如果你使用了 else-if 结构&#xff0c;则前段代码块的右大括号 } 必须和 else-if 关键字在同一行。这两条规则都是被编译器强制规定的。 格式如下&#xff1a; if cond…

linux+samba共享文件夹-window可以直接上传服务器数据(只能读取不能写入问题)

项目场景&#xff1a; 因为要上传本地瓦片100gb左右&#xff0c;下载再上传时间太长了&#xff0c;最后想到直接下载在服务器&#xff0c;但是下载瓦片软件没有linux版本&#xff0c;于是想到共享文件夹 问题描述 按照这个大佬文档(linuxsamba配置)一切都还好&#xff0c;查…

springboot 注解属性转换字典

1.注解相关功能实现 定义属性注解 import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.vehicle.manager.core.serializer.DicSerializer;import java.lang.annotation.*;/*** a…

十九、日期和时间

日期和时间 本文为书籍《Java编程的逻辑》1和《剑指Java&#xff1a;核心原理与应用实践》2阅读笔记 5.1 Date类 Date是JDK 1.0中java.util包下提供类&#xff0c;Date表示时刻&#xff0c;内部主要是一个long类型的值&#xff0c;表示特定的瞬间&#xff0c;可以精确到毫秒…

OpenAI划时代大模型——文本生成视频模型Sora作品欣赏(十一)

Sora介绍 Sora是一个能以文本描述生成视频的人工智能模型&#xff0c;由美国人工智能研究机构OpenAI开发。 Sora这一名称源于日文“空”&#xff08;そら sora&#xff09;&#xff0c;即天空之意&#xff0c;以示其无限的创造潜力。其背后的技术是在OpenAI的文本到图像生成模…

家庭游泳池:阳台上可安装的泳池

游泳池可根据场地大小选择安装在室内或室外&#xff0c;这种的泳池规格尺寸相对来说较大&#xff0c;较适合于大型体育场馆、小区配套、健身房等场所。这款家庭泳池与之前的不太一样&#xff0c;不论是从池体材料还是装饰面层都有着很大的差异。 该家庭泳池规格尺寸比较固定&a…

书籍推荐|《使用 ESP32 开发物联网项目(第二版)》

随着物联网技术的迅猛发展&#xff0c;ESP32 因其强大的功能而备受物联网开发者的青睐。在此背景下&#xff0c;资深物联网专家 Vedat Ozan Oner 撰写的《使用 ESP32 开发物联网项目&#xff08;第二版&#xff09;》&#xff0c;为开发者提供了全面且深入的指南读物。 资深物…

SpringBoot整合rabbitmq-直连队列,没有交换机(一)

说明&#xff1a;本文章只是springboot和rabbitmq的直连整合&#xff0c;只使用队列生产和消费消息&#xff0c;最简单整合&#xff01; 工程图&#xff1a; A.总体pom.xml <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://…