网站建设顺德,公司企业邮箱有哪些,合同管理软件,wordpress编辑页面改字体颜色MFA多因素认证(Multi-Factor Authentication )#xff1a; 一些需要身份认证的服务#xff08;如网站#xff09;#xff0c;为了提升安全性#xff0c;通常会在账号密码登录成功后#xff0c;要求用户进行第二种身份认证#xff0c;以确保是正确用户登录#xff0c;避…MFA多因素认证(Multi-Factor Authentication ) 一些需要身份认证的服务如网站为了提升安全性通常会在账号密码登录成功后要求用户进行第二种身份认证以确保是正确用户登录避免用户密码泄露了或其它原因导致用户信息泄露。 不过用户体验就比较差因为要登录2次嘛。 常见使用场景
企业的管理后台尤其是涉及敏感信息的系统比如CMS客户关系管理系统、财务系统等电商后台比如亚马逊店铺管理后台通常都要二次认证Git代码仓库站点上用户敏感信息的查看、修改如手机号默认隐藏中间n位要查看必须二次认证修改密码要二次认证等
常见的认证方式有
手机短信验证码验证把验证码通过短信发给用户用户在该服务里如网站上输入该验证码并认证邮箱验证码验证把验证码通过邮件发给用户用户输入该验证码并认证MFA硬件设备给用户分配一个硬件设施用于生成动态口令用户输入该口令并认证 还有一种硬件设备是插入电脑即可自动认证MFA软件用户在电脑或手机上安装一个软件软件里生成动态口令用户输入该口令并认证生物识别通过指纹、人脸识别等生物特征进行认证智能卡给用户分配一张带身份信息的卡片用户把卡片放在服务的读卡设备上进行认证
本文只介绍网站的MFA软件接入方案并采用手机应用进行认证。 只要是基于时间同步算法的手机应用都可以支持如以下应用
谷歌身份验证器google authenticator微软身份验证器Microsoft Authenticator 注这种动态口令认证通常也称之为OTP-CodeOne-time Password、OTP令牌、两步验证、二次认证、2FA等。
前端与后端的交互流程如下
后端提供3个接口
账号密码登录接口该账号是否绑定过SecureKey的接口二次验证码校验接口
完整交互流程图参考如下
SpringBoot项目接入实现
假设已经创建了SpringBoot项目。
添加依赖
打开pom.xml文件添加如下依赖
!-- 用于SecureKey生成 --
dependencygroupIdcommons-codec/groupIdartifactIdcommons-codec/artifactId
/dependency!-- 用于二维码生成 --
dependencygroupIdcom.google.zxing/groupIdartifactIdcore/artifactIdversion3.5.1/version
/dependency
dependencygroupIdcom.google.zxing/groupIdartifactIdjavase/artifactIdversion3.5.1/version
/dependencySecureKey生成与验证码生成比对类
这个是核心实现类主要功能
生成随机的SecureKey用于外部业务绑定到指定账号也用于后续的验证码生成根据SecureKey和系统时间生成相应的验证码
代码参考
package beinet.cn.googleauthenticatordemo.authenticator;import org.apache.commons.codec.binary.Base32;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;public class GoogleGenerator {// 发行者项目名可为空注不允许包含冒号public static final String ISSUER beinet.cn;// 生成的key长度( Generate secret key length)public static final int SECRET_SIZE 32;// Java实现随机数算法public static final String RANDOM_NUMBER_ALGORITHM SHA1PRNG;// 最多可偏移的时间, 假设为2表示计算前面2次、当前时间、后面2次共5个时间内的验证码static int window_size 1; // max 17static long second_per_size 30L;// 每次时间长度默认30秒/*** 生成一个SecretKey外部绑定到用户** return SecretKey*/public static String generateSecretKey() {SecureRandom sr;try {sr SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);sr.setSeed(getSeed());byte[] buffer sr.generateSeed(SECRET_SIZE);Base32 codec new Base32();byte[] bEncodedKey codec.encode(buffer);String ret new String(bEncodedKey);return ret.replaceAll($, );// 移除末尾的等号} catch (NoSuchAlgorithmException e) {// should never occur... configuration errorthrow new RuntimeException(e);}}/*** 生成二维码所需的字符串注这个format不可修改否则会导致身份验证器无法识别二维码** param user 绑定到的用户名* param secret 对应的secretKey* return 二维码字符串*/public static String getQRBarcode(String user, String secret) {if (ISSUER ! null) {if (ISSUER.contains(:)) {throw new IllegalArgumentException(Issuer cannot contain the : character.);}user ISSUER : user;}String format otpauth://totp/%s?secret%s;String ret String.format(format, user, secret);if (ISSUER ! null) {ret issuer ISSUER;}return ret;}/*** 验证用户提交的code是否匹配** param secret 用户绑定的secretKey* param code 用户输入的code* return 匹配成功与否*/public static boolean checkCode(String secret, int code) {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 timeMsec System.currentTimeMillis();long t (timeMsec / 1000L) / second_per_size;// Window is used to check codes generated in the near past.// You can use this value to tune how far youre willing to go.for (int i -window_size; i window_size; i) {int hash;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 probleme.printStackTrace();throw new RuntimeException(e.getMessage());// return false;}System.out.println(input code code ; count hash hash);if (code hash) { // addZero(hash)return true;}
/* if (codehash ) {return true;}*/}// The validation code is invalid.return false;}private static int 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, HmacSHA1);Mac mac Mac.getInstance(HmacSHA1);mac.init(signKey);byte[] hash mac.doFinal(data);int offset hash[20 - 1] 0xF;// Were using a long because Java hasnt 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 (int) truncatedHash;}private static byte[] getSeed() {String str ISSUER System.currentTimeMillis() ISSUER;return str.getBytes(StandardCharsets.UTF_8);}
}业务服务类实现
用于封装2个方法
输入账号为该账号生成并绑定SecureKey入库同时返回谷歌身份验证器所需的二维码URL输入账号和验证码获取该账号对应的SecureKey并计算当前时间的验证码与输入的验证码进行比对返回成功与否
参考实现
package beinet.cn.googleauthenticatordemo.authenticator;import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.HashMap;
import java.util.Map;Service
public class AuthenticatorService {private MapString, String userKeys new HashMap();/*** 生成一个secretKey并关联到用户* 然后返回二维码字符串** param username 用户名* return 二维码字符串*/public String generateAuthUrl(String username) {String secret GoogleGenerator.generateSecretKey();// todo: 实际项目中用户名与secretKey的关联关系应当存储在数据库里否则变化了就会无法登录userKeys.put(username, secret);return GoogleGenerator.getQRBarcode(username, secret);}/*** 根据用户名和输入的code进行校验并返回成功失败** param username 用户名* param code 输入的code* return 校验成功与否*/public boolean validateCode(String username, int code) {// todo: 从数据库里读取该用户的secretKeyString secret userKeys.get(username);if (!StringUtils.hasLength(secret)) {throw new RuntimeException(该用户未使用Google身份验证器注册请先注册);}return GoogleGenerator.checkCode(secret, code);}
}登录流程嵌入
这个根据实际代码进行修改比如前后端分离的项目
前端页面在登录成功时增加代码 判断当前用户是否绑定过未绑定时显示二维码让用户绑定弹出OTPCode输入界面二次验证成功再跳转正常业务页 后端业务接口需要判断2个Cookie都存在时才进入少一个都要返回登录失败
完整Demo代码
使用上面的代码实现的带登录二次验证的完整代码参考 https://github.com/youbl/study/tree/master/study-codes/google-authenticator-demo 可以查看该目录的历史提交记录了解每个步骤做了哪些动作
手机应用下载
Google Authenticator下载
使用比较简单手机上无需登录可直接打开。
苹果版本下载地址 https://apps.apple.com/cn/app/google-authenticator/id388497605安卓版本下载地址 https://play.google.com/store/apps/details?idcom.google.android.apps.authenticator2hlen_US国内无法访问谷歌市场的可以去这里下载apk安装 https://apkpure.com/cn/google-authenticator/com.google.android.apps.authenticator2
Microsoft Authenticator下载
有一定安全性需要进行密码或指纹验证才能打开。
苹果版本下载地址 https://apps.apple.com/us/app/microsoft-authenticator/id983156458安卓版本下载地址 https://play.google.com/store/apps/details?idcom.azure.authenticatorhlen_US国内无法访问谷歌市场的可以去这里下载apk安装 https://apkpure.com/cn/microsoft-authenticator/com.azure.authenticator