Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java集成谷歌身份验证器

Java集成谷歌身份验证器

作者头像
Blue_007
修改于 2024-06-05 09:15:28
修改于 2024-06-05 09:15:28
3.6K140
代码可运行
举报
文章被收录于专栏:代码生涯代码生涯
运行总次数:0
代码可运行

谷歌身份验证器

最近项目有需要配合谷歌身份验证器来完成业务,功能已经实现,记录下。

一、谷歌身份验证器

Google身份验证器 Google Authenticator 是谷歌推出的基于时间的一次性密码(Time-based One-time Password,简称TOTP),只需要在手机上安装该APP,就可以生成一个随着时间变化的一次性密码,用于帐户验证。

谷歌身份验证器最早是谷歌为了减少 Gmail 邮箱遭受恶意攻击而推出的两步验证方式,后来被很多网站支持。 开启谷歌身份验证之后,登录账户,除了输入用户名和密码,还需要输入谷歌验证器上的动态密码。


谷歌验证器上的动态密码,也称为一次性密码,密码按照时间或使用次数不断动态变化(默认 30 秒变更一次)。它和很多银行发行的动态口令卡类似,可以断网使用,只不过前者是谷歌推出的一个 App,后者是专门的一个硬件。

大家都知道我们平常登录一个网站的时候,会输入账号、密码,有些也会输入短信验证码(也是为了提高安全性),有些网站除了以上这些之外,还需要输入一次动态口令才能验证成功。这个动态口令就是Google身份验证器每隔30s会动态生成一个6位数的数字。它的作用是:对你的账号进行“二步验证”保护,或者说做一个双重身份验证,来达到提升安全级别的目的。

二、谷歌验证 (Google Authenticator) 的实现原理

实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。

2.1 用户需要开启Google Authenticator服务时

  1. 服务器随机生成一个类似于『DPI45HKISEXU6HG7』的密钥,并且把这个密钥保存在数据库中;
  2. 在页面上显示一个二维码,内容是一个URI地址(otpauth://totp/账号?secret=密钥),如:otpauth://totp/kisexu@gmail.com?secret=DPI45HCEBCJK6HG7 (二维码自动识别)
  3. 客户端扫描二维码,把密钥『DPI45HKISEXU6HG7』保存在客户端 (手机上的Google APP)。

2.2 用户需要登录时

  1. 客户端每30秒使用密钥『DPI45HKISEXU6HG7』和时间戳通过一种『算法』生成一个6位数字的一次性密码,如『684060』。
  2. 用户登录时输入一次性密码『684060』。
  3. 服务器端使用保存在数据库中的密钥『DPI45HKISEXU6HG7』和时间戳通过同一种『算法』生成一个6位数字的一次性密码。如果算法相同、密钥相同,又是同一个时间(时间戳相同),那么客户端和服务器计算出的一次性密码是一样的。服务器验证时如果一样,就登录成功了。

这种『算法』是公开的,所以服务器端也有很多开源的实现。

本质上是基于共享密钥的身份认证,当你从银行领取一个动态令牌时,已经做过了 密钥分发Google Authenticator 的二维码绑定过程其实就是 密钥分发 的过程而已。实现方式主要分为两种:HOTP,TOTP,国内主要使用TOTP,因为时间同步并不是太难的事。

原理请参看RFC4226:https://www.ietf.org/rfc/rfc4226.txt

客户端和服务器事先协商好一个密钥K,用于一次性密码的生成过程,此密钥不被任何第三方所知道。此外,客户端和服务器各有一个计数器C,并且事先将计数值同步。

进行验证时,客户端对密钥和计数器的组合(K,C)使用HMAC(Hash-based Message Authentication Code)算法计算一次性密码

公式如下:HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))

上面采用了HMAC-SHA-1,当然也可以使用HMAC-MD5等。

HMAC算法得出的值位数比较多,不方便用户输入,因此需要截断成为一组不太长十进制数(例如6位)。计算完成之后客户端计数器C计数值加1。用户将这一组十进制数输入并且提交之后,服务器端同样的计算,并且与用户提交的数值比较,如果相同,则验证通过,服务器端将计数值C增加1。如果不相同,则验证失败。

三、Java代码实现

3.1 Controller

为了方便看,services层代码逻辑我也整合过来了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import com.xx.untils.GoogleAuthenticator;
import com.xx.untils.GoogleGenerator;
import com.xx.untils.QrCodeUtils;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import static com.xx.untils.RandomUtils.getNum;

/**
 * 谷歌验证-Controller
 * @ClassName AsurPlusController
 * @Author Blue Email:2113438464@qq.com
 * @Date 2022
 */
@Api(tags = "谷歌验证")
@RestController
@RequestMapping("/asurplus")
@CrossOrigin
@Slf4j
public class AsurplusController {

    /**
     * 生成 Google 密钥,两种方式任选一种
     * @return 密钥字符
     */
    @ApiOperation(value = "获取Google 密钥")
    @GetMapping("/getSecretKey")
    public String getSecretKey() {
        return GoogleAuthenticator.getSecretKey();
    }
    
    /**
     * 生成 Google 密钥后 转二维码 转base64Pic,两种方式任选一种
     * 可以先请求getSecretKey()方法后,获得密钥字符后,将密钥字符做为参数 调用本方法
     * @param secretKey 密钥
     * @return base64
     */
    @ApiOperation(value = "获取Google 二维码")
    @ApiImplicitParam(name = "secretKey",value = "密钥",required = true)
    @GetMapping("/getQrcodes")
    public String getQrcodes(@RequestParam String secretKey) throws Exception {
        Long num = getNum(5);//随机生成五位的码,当做账号名
        String base64Pic = QrCodeUtils.creatRrCode(GoogleAuthenticator.getQrCodeText(secretKey,num.toString(),""), 200,200);
        return base64Pic;
    }

    /**
     * 获取Google code
     * @param secretKey 密钥
     * @return 验证码
     */
    @ApiOperation(value = "获取Google code")
    @ApiImplicitParam(name = "secretKey",value = "密钥",required = true)
    @GetMapping("/getCode")
    public String getCode(@RequestParam("secretKey") String secretKey) {
        return GoogleAuthenticator.getCode(secretKey);
    }

    /**
     * 验证Google code 是否正确
     * @param secretKey 密钥
     * @param code 验证码
     * @return Boolean
     */
    @ApiOperation(value = "验证Google code 是否正确")
    @ApiImplicitParams(value = {
        @ApiImplicitParam(name = "secretKey",value = "密钥",required = true),
        @ApiImplicitParam(name = "code",value = "验证码",required = true)
    })
    @GetMapping("/checkCode")
    public Boolean checkCode(@RequestParam("secretKey") String secretKey, @RequestParam("code") String code) {
        return GoogleAuthenticator.checkCode(secretKey, Long.parseLong(code), System.currentTimeMillis());
    }
    
    /**
     * 判断是否绑定谷歌验证
     * @param addr 业务用户标识
     * @return Result
     */
    @ApiOperation(value = "判断是否绑定谷歌验证")
    @ApiImplicitParam(name = "addr",value = "业务用户标识",required = true)
    @PostMapping(value = "/google")
    public Result Google(@RequestParam String addr) {
        // 业务代码,可以根据自己的场景进行修改
        Users users1 = usersService.getBaseMapper().selectOne(new LambdaQueryWrapper<Users>().eq(Users::getAddr, addr));
        if (StringUtils.isEmpty(users1.getGoogleToken())){
            return Result.succeed(Result.fail("没有绑定谷歌验证"));
        }else{
            return Result.succeed(Result.succeed("true"));
        }
    }
    
    /**
     * 绑定谷歌
     * @param addr 业务用户标识
     * @param googleToken 密钥字符
     * @return Result
     */
    @ApiOperation("绑定谷歌")
    @ApiImplicitParams(value = {
        @ApiImplicitParam(name = "addr",value = "业务用户标识",required = true),
        @ApiImplicitParam(name = "googleToken",value = "密钥字符",required = true)
    })
    @PostMapping(value = "/googleSave")
    public Result googleSave(@RequestParam(required = false) String addr,@RequestParam(required = false)  String googleToken,@RequestParam(required = false)  String code) {
        // 安全参数
        if (StringUtils.isEmpty(addr)){
            return Result.succeed(Result.fail("用户地址不能为空"));
        }
        if (StringUtils.isEmpty(googleToken)){
            return Result.succeed(Result.fail("谷歌验证不能为空"));
        }
        if (StringUtils.isEmpty(code)){
            return Result.succeed(Result.fail("验证码不能为空"));
        }
        // 根据用户地址查询用户,业务需求,根id主键一个作用
        Users users = usersService.getBaseMapper().selectOne(new LambdaQueryWrapper<Users>().eq(Users:getAddr, addr));
        if (ObjectUtil.isNotNull(users)) {
            
            // Users实体类和数据表中的两个属性 需要自己创建 分别为:
            // googleToken 存放 谷歌验证的token
            // googleStatus谷歌验证状态   0未绑定  1绑定
            if ("1".equals(users.getGoogleStatus())){
                return Result.fail("用户已绑定过谷歌验证");
            }
            log.info("googleSave()-googleToken=="+googleToken);
            log.info("googleSave()-code=="+code);
            
            // 验证Google code 是否正确
            boolean b = GoogleAuthenticator.checkCode(googleToken, Long.parseLong(code), System.currentTimeMillis());
            if (!b){
                return Result.succeed(Result.fail("绑定的秘钥不正确"));
            }
            
            // 修改
            users.setGoogleStatus("1");
            users.setGoogleToken(googleToken);
            users.setupdatedAt(new Date());
            
            if (usersService.getBaseMapper().updateById(users)>0){
                return Result.succeed(Result.succeed("绑定成功"));
            }else{
                return Result.succeed(Result.succeed("绑定失败"));
            }
            
        }else{
            return Result.succeed(Result.fail("绑定地址不存在"));
        }
    }
    
}

3.2 untils

谷歌身份验证器工具类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;
import org.springframework.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * 谷歌身份验证器工具类
 * @ClassName GoogleAuthenticator
 * @Author Blue Email:2113438464@qq.com
 * @Date 2022
 */
public class GoogleAuthenticator {

    /**
     * 时间前后偏移量
     * 用于防止客户端时间不精确导致生成的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 static int WINDOW_SIZE = 0;

    /**
     * 加密方式,HmacSHA1、HmacSHA256、HmacSHA512
     */
    private static final String CRYPTO = "HmacSHA1";

    /**
     * 生成密钥,每个用户独享一份密钥
     * @return
     */
    public static String getSecretKey() {
        SecureRandom random = new SecureRandom();
//        byte[] bytes = new byte[20];
        byte[] bytes = new byte[10];
        random.nextBytes(bytes);
        Base32 base32 = new Base32();
        String 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
     */
    public static String getQrCodeText(String secretKey, String account, String issuer) {
        String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();
        try {
            return "otpauth://totp/"
                    + URLEncoder.encode((!StringUtils.isEmpty(issuer) ? (issuer + ":") : "") + account, "UTF-8").replace("+", "%20")
                    + "?secret=" + URLEncoder.encode(normalizedBase32Key, "UTF-8").replace("+", "%20")
                    + (!StringUtils.isEmpty(issuer) ? ("&issuer=" + URLEncoder.encode(issuer, "UTF-8").replace("+", "%20")) : "");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * 获取验证码
     * @param secretKey
     * @return
     */
    public static String getCode(String secretKey) {
        String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();
        Base32 base32 = new Base32();
        byte[] bytes = base32.decode(normalizedBase32Key);
        String hexKey = Hex.encodeHexString(bytes);
        long time = (System.currentTimeMillis() / 1000) / 30;
        String hexTime = Long.toHexString(time);
        return TOTP.generateTOTP(hexKey, hexTime, "6", CRYPTO);
    }

    /**
     * 检验 code 是否正确
     * @param secret 密钥
     * @param code   code
     * @param time   时间戳
     * @return
     */
    public static boolean checkCode(String secret, long code, long time) {
        Base32 codec = new Base32();
        byte[] decodedKey = codec.decode(secret);
        // convert unix msec time into a 30 second "window"
        // this is per the TOTP spec (see the RFC for details)
        long 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.
        long hash;
        for (int i = -WINDOW_SIZE; i <= WINDOW_SIZE; ++i) {
            try {
                hash = verifyCode(decodedKey, t + i);
            } catch (Exception e) {
                // Yes, this is bad form - but
                // the exceptions thrown would be rare and a static
                // configuration problem
                // e.printStackTrace();

//                throw new RuntimeException(e.getMessage());
                return false;

            }
            if (hash == code) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据时间偏移量计算
     * @param key
     * @param t
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    private static long verifyCode(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
        byte[] data = new byte[8];
        long value = t;
        for (int i = 8; i-- > 0; value >>>= 8) {
            data[i] = (byte) value;
        }
        SecretKeySpec signKey = new SecretKeySpec(key, CRYPTO);
        Mac mac = Mac.getInstance(CRYPTO);
        mac.init(signKey);
        byte[] hash = mac.doFinal(data);
        int offset = hash[20 - 1] & 0xF;
        // We're using a long because Java hasn't got unsigned int.
        long truncatedHash = 0;
        for (int i = 0; i < 4; ++i) {
            truncatedHash <<= 8;
            // We are dealing with signed bytes:
            // we just keep the first byte.
            truncatedHash |= (hash[offset + i] & 0xFF);
        }
        truncatedHash &= 0x7FFFFFFF;
        truncatedHash %= 1000000;
        return truncatedHash;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            String secretKey = getSecretKey();
            System.out.println("secretKey:" + secretKey);
            String code = getCode(secretKey);
            System.out.println("code:" + code);
            boolean b = checkCode(secretKey, Long.parseLong(code), System.currentTimeMillis());
            System.out.println("isSuccess:" + b);
        }
    }
}

图片转换工具类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import org.apache.commons.codec.binary.Base64;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Hashtable;

/**
 * URL转Base64二维码
 * @ClassName QrCodeUtils
 * @Author Blue Email:2113438464@qq.com
 * @Date 2022
 */
public class QrCodeUtils {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static String creatRrCode(String contents, int width, int height) {
    String base64 = "";
        
        Hashtable hints = new Hashtable();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        
        try {
            BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, width, height, hints);
            
            // 1、读取文件转换为字节数组
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            BufferedImage image = toBufferedImage(bitMatrix);
            
            //转换成png格式的IO流
            ImageIO.write(image, "png", out);
            byte[] bytes = out.toByteArray();
 
            // 2、将字节数组转为二进制
            base64 = Base64.encodeBase64String(bytes).trim();
            
        } catch (WriterException e) {
            e.printStackTrace();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return base64;
    }
 
    /**
     * image流数据处理
     */
    private static BufferedImage toBufferedImage(BitMatrix matrix) {
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        return image;
    }
 
    public static void main(String[] args) {
      // 测试代码
        String base64Pic = QrCodeUtils.creatRrCode("http://zf.thxyy.cn/weixinmpPlus/byCodePay/list?dd=JC2101080005&ts=1610080940", 200,200);
        System.out.println(base64Pic);
    }
}

验证码生成工具类

代码语言:java
AI代码解释
复制
package com.qhzx.ycheng.untils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;

/**
 * 验证码生成工具类
 */
public class TOTP {

    // 0 1 2 3 4 5 6 7 8
    private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};

    /**
     * This method uses the JCE to provide the crypto algorithm. HMAC computes a
     * Hashed Message Authentication Code with the crypto hash algorithm as a
     * parameter.
     *
     * @param crypto   : the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
     * @param keyBytes : the bytes to use for the HMAC key
     * @param text     : the message or text to be authenticated
     */
    private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
        try {
            Mac hmac;
            hmac = Mac.getInstance(crypto);
            SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
            hmac.init(macKey);
            return hmac.doFinal(text);
        } catch (GeneralSecurityException gse) {
            throw new UndeclaredThrowableException(gse);
        }
    }

    /**
     * This method converts a HEX string to Byte[]
     *
     * @param hex : the HEX string
     * @return: a byte array
     */
    private static byte[] hexStr2Bytes(String hex) {
        // Adding one byte to get the right conversion
        // Values starting with "0" can be converted
        byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();

        // Copy all the REAL bytes, not the "first"
        byte[] ret = new byte[bArray.length - 1];
        System.arraycopy(bArray, 1, ret, 0, ret.length);
        return ret;
    }

    /**
     * This method generates a TOTP value for the given set of parameters.
     *
     * @param key          : the shared secret, HEX encoded
     * @param time         : a value that reflects a time
     * @param returnDigits : number of digits to return
     * @param crypto       : the crypto function to use
     * @return: a numeric String in base 10 that includes
     */
    public static String generateTOTP(String key, String time, String returnDigits, String crypto) {
        int codeDigits = Integer.decode(returnDigits);
        String result = null;

        // Using the counter
        // First 8 bytes are for the movingFactor
        // Compliant with base RFC 4226 (HOTP)
        while (time.length() < 16) {
            time = "0" + time;
        }

        // Get the HEX in a Byte[]
        byte[] msg = hexStr2Bytes(time);
        byte[] k = hexStr2Bytes(key);
        byte[] hash = hmac_sha(crypto, k, msg);

        // put selected bytes into result int
        int offset = hash[hash.length - 1] & 0xf;

        int binary = ((hash[offset] & 0x7f) << 24)
                | ((hash[offset + 1] & 0xff) << 16)
                | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);

        int otp = binary % DIGITS_POWER[codeDigits];

        result = Integer.toString(otp);
        while (result.length() < codeDigits) {
            result = "0" + result;
        }
        return result;
    }
}

OK,像Java集合谷歌验证网上有很多案例,但大多数都是照搬,无法使用,本案例已通过测试并完整使用过。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 代码生涯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
14 条评论
热度
最新
请问下: getNum(5);//随机生成五位的码,当做账号名 这里的getNum里面是什么逻辑呢?
请问下: getNum(5);//随机生成五位的码,当做账号名 这里的getNum里面是什么逻辑呢?
11点赞举报
没有什么逻辑,你可以理解为给用户的令牌一个标识(名称),比如:令牌1、令牌2、软件名等等,只是让用户能够区分不同的令牌
没有什么逻辑,你可以理解为给用户的令牌一个标识(名称),比如:令牌1、令牌2、软件名等等,只是让用户能够区分不同的令牌
回复回复点赞举报
兄弟那个 TOTP 类,你没贴。TOTP.generateTOTP(hexKey, hexTime, "6", CRYPTO); 这里会报错,能贴出来吗
兄弟那个 TOTP 类,你没贴。TOTP.generateTOTP(hexKey, hexTime, "6", CRYPTO); 这里会报错,能贴出来吗
22点赞举报
好的 等下
好的 等下
回复回复点赞举报
文章还在审核,你可以先加我微信hk_031300,我发代码给你
文章还在审核,你可以先加我微信hk_031300,我发代码给你
回复回复点赞举报
生成的二维码,使用谷歌认证扫描后,服务端怎么知道已经扫描(绑定)?因为服务端需要记录用户是否绑定谷歌认证,下次就不用绑定了
生成的二维码,使用谷歌认证扫描后,服务端怎么知道已经扫描(绑定)?因为服务端需要记录用户是否绑定谷歌认证,下次就不用绑定了
77点赞举报
很简单,使用谷歌认证扫描后,谷歌认证器中会出现验证码,下一步就是让用户和谷歌密钥绑定在一起 一般前端页面流程是:用户扫描生成的二维码后,点击下一步进行绑定,这个时候用户需要输入 `谷歌认证器中对应的验证码`,同时前端需要把对应的密钥回传过去,你可以在本文全局查找一下“googleSave”和“getCode”这两个方法,流程已经有了
很简单,使用谷歌认证扫描后,谷歌认证器中会出现验证码,下一步就是让用户和谷歌密钥绑定在一起 一般前端页面流程是:用户扫描生成的二维码后,点击下一步进行绑定,这个时候用户需要输入 `谷歌认证器中对应的验证码`,同时前端需要把对应的密钥回传过去,你可以在本文全局查找一下“googleSave”和“getCode”这两个方法,流程已经有了
回复回复点赞举报
一般来说,用户表会有两个字段,分别为 `google_str` == '用户谷歌密钥'==没有绑定就为空 `google_status`=='用户谷歌验证状态 0-无 1-有',
一般来说,用户表会有两个字段,分别为 `google_str` == '用户谷歌密钥'==没有绑定就为空 `google_status`=='用户谷歌验证状态 0-无 1-有',
回复回复点赞举报
查看全部7条回复
绝拉
绝拉
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
两步验证杀手锏:Java 接入 Google 身份验证器实战
大家应该对两步验证都熟悉吧?如苹果有自带的两步验证策略,防止用户账号密码被盗而锁定手机进行敲诈,这种例子屡见不鲜,所以苹果都建议大家开启两步验证的。
Java技术栈
2018/09/29
5.3K0
为 Linux 服务器 SSH 添加 TOTP 动态验证码以及 Java 实现算法
TOTP 基于时间的一次性密码算法(Time-Based One-Time Password)是一种根据预共享的密钥与当前时间计算一次性密码的算法,利用不同设备时间相同的特性,将时间作为特定算法的一部分从而达到无需网络进行安全验证的目的。 该算法有两个输入,一个输出,一个输入是随机生成的密钥,密钥需要被验证方和验证器同时持有,另一个输入即系统时间,通常是 UNIX 时,输出则是两方相同的验证码。一般的验证码有效期为 30 秒,每 30
寒冰hanbings
2021/12/21
1.9K0
为 Linux 服务器 SSH 添加 TOTP 动态验证码以及 Java 实现算法
动态令牌_创建安全令牌
HMAC-based One-Time Password 简写,表示基于 HMAC 算法加密的一次性密码。是事件同步,通过某一特定的事件次序及相同的种子值作为输入,通过 HASH 算法运算出一致的密码。
全栈程序员站长
2022/11/17
1.6K0
动态令牌_创建安全令牌
如何选择合适的用户身份验证方法
选择合适的用户身份验证方法需要考虑多个因素,包括安全性、用户体验、应用场景和技术实现等。以下是一些常见的用户身份验证方法,以及选择时需要考虑的关键因素:
华科云商小徐
2024/07/12
2560
使用aerogear生成totp
aerogear-otp-java-1.0.0-sources.jar!/org/jboss/aerogear/security/otp/Totp.java
code4it
2018/09/17
1.8K0
加密与安全_深入了解Hmac算法(消息认证码)
这个salt可以看作是一个额外的“认证码”,同样的输入,不同的认证码,会产生不同的输出。因此,要验证输出的哈希,必须同时提供“认证码”。
小小工匠
2024/05/26
1.1K0
JAVA与PHP之间进行aes加密解密
用户数据应经过加密再传输,此文档为aes128加密(cbc模式)的说明 摘要算法为SHA-512
BUG弄潮儿
2022/06/30
2.7K0
Java中4大基本加密算法解析
简单的java加密算法有: BASE64 严格地说,属于编码格式,而非加密算法 MD5(Message Digest algorithm 5,信息摘要算法) SHA(Secure Hash Algorithm,安全散列算法) HMAC(Hash Message Authentication Code,散列消息鉴别码) 1. BASE64 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。 Base64编码可用于在HTTP
我是攻城师
2018/05/11
1.9K0
Java使用AES加密解密
AES加密机制: 密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。 这个标准用来替代原先的DES(Data Encryption Standard),已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院 (NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中
二十三年蝉
2018/08/01
5.1K0
Java加密与解密之消息摘要算法
消息摘要算法又称为散列算法,其核心在于散列函数的单向性。即通过散列函数可获得对应的散列值,但不可通过该散列值反推其原始信息。这是消息摘要算法的安全性的根本所在。消息摘要算法主要分为三大类:MD(MessageDigest,消息摘要算法)、SHA(Secure HashAlgorithm,安全散列算法)和MAC(MessageAuthentication Code,消息认证码算法)。MD5、SHA和HMAC分别是三大类消息摘要算法中的代表。
布禾
2020/11/24
8970
Java加密与解密之消息摘要算法
谷歌authenticator接入与使用
一、authenticator解决了什么问题二、authenticator的原理三、springboot集成authenticator四、做成可复用starter五、参考
叔牙
2023/08/09
6.3K1
谷歌authenticator接入与使用
使用越来越广泛的2FA双因素认证,缘何越发受到推崇?
随着互联网在生活方方面面的应用,日常少不了要登录各个网站或者应用、或者是银行转账等需要验证自己身份的场景。从早期的输入账号密码来登录,到后来普遍开始通过手机验证码进行登录、或者APP扫码进行登录,身份校验的操作方式经历了一轮又一轮的迭代演进。
是Vzn呀
2024/11/22
2161
Java整合Google身份验证器:详解及实战应用
咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
bug菌
2024/12/29
3322
Java整合Google身份验证器:详解及实战应用
动态令牌之 OTP,HOTP,TOTP 的基本原理 Python
OTP 是 One-Time Password的简写,标识一次性密码HOTP 是HMAC-based One-Time Password的简写,表示基于HMAC算法加密的一次性密码。是事件同步,通过某一特定的事件次序及相同的种子值作为输入,通过HASH算法运算出一致的密码。(基于事件)TOTP 是Time-based One-Time Password的简写,表示基于时间戳算法的一次性密码。是时间同步,基于客户端的动态口令和动态口令验证服务器的时间比对,一般每60秒产生一个新口令,要求客户端和服务器能够十分精确的保持正确的时钟,客户端和服务端基于时间计算的动态口令才能一致。
用户7886150
2020/12/30
2.5K0
区块链基础:非对称算法
1.Hash算法 package cn.hadron.security; import java.security.MessageDigest; import java.util.UUID; import org.eclipse.jetty.util.security.Credential.MD5; /** * crypto['krɪptoʊ]秘密成员, * 一些语言的crypto模块的目的是为了提供通用的加密和哈希算法 * 加密工具类 */ public class CryptoUtil
程裕强
2022/05/06
3870
区块链基础:非对称算法
哈希算法是对称算法还是非对称算法_对称加密和非对称加密原理
作用:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。 哈希算法的目的:为了验证原始数据是否被篡改。 哈希算法最重要的特点就是: 相同的输入一定得到相同的输出; 不同的输入大概率得到不同的输出。
全栈程序员站长
2022/11/01
1.2K0
身份验证器是如何验证我们的身份?
​ 我以为我最初遇见他是在宝塔面板上,因为他可以方便的帮助我们进行身份验证。其实我们早就相遇在QQ安全中心手机版的口令里面(此处不确定是否是使用同一种算法,不过原理类似)。当初遇见他,我并不知道他是离线的。我以为谷歌身份验证器肯定是绑定谷歌账号的。后来找了半天,原来他只是个离线的软件。相信有很多同学和我一样的想法:离线身份验证器如何能使我们登录在线的场景?
yumusb
2020/08/28
4.3K0
基于python+PyQt5的Google身份验证器
1.本文学习nanhuier的博客《Python计算谷歌身份验证器的验证码》并优化其中代码。 原博客链接:https://blog.csdn.net/nanhuier/article/details/77679200 2.本文学习莫水千流的博客《程序员之路:python3+PyQt5+pycharm桌面GUI开发》, 成功搭建PyQt5+Pycharm的开发环境,建议读者先按照此文配置好环境。 原博客链接:https://www.cnblogs.com/zhoug2020/p/9039993.html 3.本文学习maicss的github工程《PyQt5-Chinese-tutorial》 github链接:https://github.com/maicss/PyQt5-Chinese-tutorial 4.本文学习晴空行的博客《Python打包方法》, 原博客链接:https://www.cnblogs.com/gopythoner/p/6337543.html
潇洒坤
2019/03/06
1.4K0
CKafka系列学习文章 - 用java实现API调用(十一)
导语:上一章节我们了解了怎么手动拼接请求URL和用API2.0签名自动生成请求URL,来调用CKafka的接口,这一章将进入开发阶段,用java实现拼接请求URL,进行API调用。
发哥说消息队列
2019/09/06
6640
Java中的加密与安全,你了解多少
  什么是数据安全?假如Bob要给Alice发送一封邮件,在发送邮件的过程中,黑客可能会窃取到邮件的内容,所以我们需要防窃听;黑客也有可能会篡改邮件的内容,所以Alice必须要有能有去识别邮件是否被篡改;最后,黑客也可能假冒Bob给Alice发送邮件,所以Alice还必须有能力识别出伪造的邮件。所以数据安全的几个要点就是:防窃听、防篡改和防伪造。 古代的加密方式:
程序员波特
2024/01/19
2370
Java中的加密与安全,你了解多少
推荐阅读
相关推荐
两步验证杀手锏:Java 接入 Google 身份验证器实战
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验