双因子认证系统登录模块

实现原理:

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

二、用户需要登陆时

1.客户端每30秒使用密钥『DPI45HKISEXU6HG7』和时间戳通过一种『算法』生成一个6位数字的一次性密码,如『684060』。如下图android版界面:

2.用户登陆时输入一次性密码『684060』。

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

一. 编程思路(实验原理)

使用的是谷歌身份验证器

加盐哈希:存放帐号的数据库经常成为入侵的目标,所以你必须做点什么来保护密码,以防网站被攻破时发生危险。最好的办法就是对密码进行加盐哈希。在密码中混入一段“随机”的字符串再进行哈希加密,这个被字符串被称作盐值。这使得同一个密码每次都被加密为完全不同的字符串。为了校验密码是否正确,我们需要储存盐值。通常和密码哈希值一起存放在账户数据库中,或者直接存为哈希字符串的一部分。
Google身份验证器:客户端和服务器事先协商号一个密钥K,用于一次性密码的生成过程,此密钥不被任何第三方知道,此外,客户端和服务器各有一个计数器C,并且事先将计数值同步,进行验证时,客户端对密钥和计数器的组合(k.c)使用的HMAC(Hash-based Message Authentication Code)

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

谷歌身份验证器

Google-Authenticator默认认证方式为密码+验证码

Google-authenticator是基于时间的一次性密码算法(TOTP)是一种根据预共享的密钥与当前时间计算一次性密码的算法

TOTP是散列消息认证码(HMAC)当中的一个例子。它结合一个私钥与当前时间戳,使用一个密码散列函数来生成一次性密码。由于网络延迟与时钟不同步可能导致密码接收者不得不尝试多次遇到正确的时间来进行身份验证,时间戳通常以30秒为间隔,从而避免反复尝试。

加密算法

SALT值属于随机值。用户注册时,系统用来和用户密码进行组合而生成的随机数值,称作salt值,通称为加盐值。

原理:为用户密码添加Salt值,使得加密的得到的密文更加冷僻,不宜查询。即使黑客有密文查询到的值,也是加了salt值的密码,而非用户设置的密码。salt值是随机生成的一组字符串,可以包括随机的大小写字母、数字、字符,位数可以根据要求而不一样。

HMAC算法是一种基于密钥的报文完整性的验证方法,其安全性是建立在HASH加密算法的基础上的,要求通信双方共享密钥,约定算法,对报文进行Hash运算,形成固定长度的认证码。

通信双方通过认证码的校验来确定报文的合法性,用来加密,数字签名 ,报文验证。(实际的使用中用HMAC做加密也是不可逆加密的,不像用DES/AES这种可逆加密,感觉HMAC和随机盐HASH算法非常像)

总结:HMAC是密钥相关的哈希运算消息认证码(hash-based-Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。

HMAC算法是一种执行“校验和”的算法,他通过对数据进行校验来检查数据是否被更改了,在发送数据以前,HMAC算法对数据块和双方约定的公钥进行“散列操作”,以生成称为“摘要”的东西,附加在待发送的数据块中。当数据和摘要到达其目的地时,就使用HMAC算法来生成另一个校验和,如果两个数字相匹配,那么数据未被做任何篡改。否则,就意味着数据在传输或存储过程中被某些居心叵测的人作了手脚。

通过哈希算法,我们可以验证一段数据是否有效,方法就是对比该数据的哈希值,例如,判断用户口令是否正确,我们用保存在数据库中的password_md5对比md5(password)的结果,如果一致,用户输入的口令就是正确的。

为了防止黑客使用彩虹表根据哈希值反推原始口令,在计算哈希的时候,不能值针对原始输入计算,需要增加一个salt来使得相同的输入也能得到不同的哈希。

如果salt是我们自己随机生成的,通常我们计算MD5采用(message + salt),我们把salt看做成一个口令,加salt的哈希:计算一段的message的哈希时,根据不同口令计算出不同的哈希,验证哈希值,必须提供正确的口令。

实际上就是HMAC算法:Keyed-Hashing for Message Authentication

在计算哈希的过程中,把key混入计算过程中

和我们自定义的加的salt算法不同,hmac算法针对所有的哈希算法都是通用的。采用hmac来替代salt算法.

使用场景:该场景就是用来验证文件的安全性

服务端生成key,传给客户端

客户端使用key将账号和密码做HMAC,生成一串散列值,传给服务端

服务端使用key和数据库中用户和密码作为HMAC的散列值,比对来客户端的散列值

HMAC的典型应用:该应用提供了一次安全相应的过程

挑战/响应(“Challenge/Response")身份验证中。

  1. 先由客户端向服务器发出一个验证请求
  2. 服务器接到此请求后生成一个随机数并通过网络传输给客户端(此为挑战)
  3. 客户端将收到的随机数与自己的密钥进行HMAC-SHA1运算并得到一个结果并作为认证的证据传给服务器
  4. 与此同时,服务器也是用该随机数与储存在服务器数据库中的该客户的密钥进行HMAC-SHA1运算,如果服务器的运算结果与客户端传回的响应结果相同,就认为客户端是一个合法用户

Allocate the operation handle
分配一个进行密码操作句柄,并设置密码的类型和方式

TEE_Result TEE_AllocateOperation(TEE_OperationHandle *operation,uint32_t algorithm, uint32_t mode,uint32_t maxKeySize)

分配一个未初始化的临时空间

TEE_Result TEE_AllocateTransientObject(TEE_ObjectType objectType,uint32_t maxKeySize,TEE_ObjectHandle *object)

将属性变量填充/赋值到空间变量里

TEE_Result TEE_PopulateTransientObject(TEE_ObjectHandle object,const TEE_Attribute *attrs,uint32_t attrCount)

把存放密钥的Object内容保存到句柄中
将保存key得object保存到handle

TEE_Result TEE_SetOperationKey(TEE_OperationHandle operation,TEE_ObjectHandle key)

分配一个未初始化的临时空间

TEE_Result TEE_AllocateTransientObject(TEE_ObjectType objectType,uint32_t maxKeySize,TEE_ObjectHandle *object)

导出key

void TEE_DeriveKey(TEE_OperationHandle operation,const TEE_Attribute *params, uint32_t paramCount,TEE_ObjectHandle derivedKey)

TEE_GetObjectBufferAttribute函数的作用是从对象中提取一个缓冲区属性

TEE_Result TEE_GetObjectBufferAttribute(TEE_ObjectHandle object,uint32_t attributeID, void *buffer,uint32_t *size)

1、Cryptographic Operations API 加解密函数介绍()

(1)、Generic Operation Functions TEE_AllocateOperation TEE_FreeOperation TEE_SetOperationKey TEE_SetOperationKey2 (2)、Message Digest Functions 消息摘要 TEE_DigestUpdate TEE_DigestDoFinal (3)、Symmetric Cipher Functions 对称加解密 TEE_CipherInit TEE_CipherUpdate TEE_CipherDoFinal (4)、Asymmetric Functions 非对称加解密 TEE_AsymmetricEncrypt, TEE_AsymmetricDecrypt TEE_AsymmetricSignDigest 和 TEE_AsymmetricVerifyDigest (4)、Random Data Generation Function 随机数 TEE_GenerateRandom

二.實現

使用密钥和时间戳通过一种算法生成一个6位数字的一次性验证码

实现流程

1、手机下载 Google AuthenticatorIOS:在App Store 搜索 Google AuthenticatorAndroid:打开连接 https://en.softonic.com/download右上角搜索 Google Authenticator选择平台 Platform:Android点击下方 Google Authenticator点击 FREE DOWNLOAD再点击 FREE APK DOWNLOAD 等待下载完成
2、生成Secret调用代码中的 genSecret() 方法生成,记得保存起来,用户需要使用
3、用户扫描二维化进行绑定(也可以拿到密钥手动绑定)1、当前用户生成的密钥可以生成二维码,用户用app右下角扫描二维码即可添加参考代码中的main方法2、手动添加这里需要知道密钥,添加上名称和密钥即可
4、输入用户名、密码、动态码
5、登录成功
 <dependencies><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.14</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.4.1</version></dependency>    <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency></dependencies>

核心代碼分析

  • 首先用戶注冊賬號,用戶輸入的注冊賬號和密碼傳入後端

    对于原始秘密进行加盐哈希加密原始密码,同时随机生成一个用于谷歌身份验证的密钥。后台数据会保存加密后的密钥,秘密盐值,以及用于谷歌身份验证的密钥。

    public String add(User user) {passwordHelper.encryptPassword(user);//加密密碼user.setSecret(GoogleAuthenticator.generatrSecretKey());//生成一个随机密钥this.insert(user);return GoogleAuthenticator.getQRBarcode(user.getUsername(),user.getSecret());//生成一个Google身份验证器识别的字符串,把该返回值传回给前台生成二维码
    }
    public void encryptPassword(USer user) {try {PsdProcRlt r;//对原始密码进行加密处理,处理后得到salt以及密文r = CryptoApp.PwdProcess(user.getPassword().getBytes());user.setSalt(Utils.bytesToHexString(r.getSalt()));//设置salt值user.setPassword(Utils.bytesToHexString(r.getValue()));//设置新的密码} catch (Exception e)
    }
    

以下代码用于随机生成密钥

public static String generateSecretKey() {SecureRandom sr = null;try {sr = SecrueRandom.getInstance(RANDOM_NUMBER_ALGORITHM);sr.setSeed(Base64.decodeBase64(SEED));byte[] buffer = sr.generateSeed(SECRET_SIZE);Base32 codec = new Base32();byte[] buffer = codec.encode(buffer);String encodeKey = new String(bEncodedKey);return encodeKey;} catch (NoSuchAlgorithmException e) {}return null;
}
//生成一個Google身份验证器识别的字符串,把该方法返回值生成一个二维码扫描
public static String getQRBarcide(String user, String secret) {String format = "otpauth://totp/%s?secret=%s";return String.format(format,user,secret);
}

2.客户端和服务器各有一个计数器C,微信小程序二次验证码扫描二维码, 将计数值同步

3.当用户进入登录页,输入账号,密码登录后,系统先对用户输入的密码进行初次验证(双因子认证,第一次认证)

初次驗證
public boolean passwordVerofy(String password,User user) {boolean match = false;Object subCredentials = null;try{subCredentials = CryptoApp.PwdTransVlaue(password.getBytes(),PublicUtils.hexStringToBytes(user.getSalt());}catch(Exception e){e.printStackTrace();return false;}byte[] accountCredentials = Utils.hexStringToBytes(user.getPassword());match = equals(subCredentials,accountCredentials);return match;
}

4.初次密码认证通过之后,系统会弹出弹框,要求输入口令,这个口令就是小程序中谷歌身份验证器产生的随机code(双因子认证,第二次认证)

public boolean googleAuthVertify(Integer userid,long code) {User user = userService.selectById(userid);long t = System.currentTimeMillis();GoogleAuthenticator ga = new GoodleAuthenticator();ga.setWindowSiza(5);return ga.check_code(user.getSecret(),code,t);
}
//Google  Authenticator  // 只从google出了双重身份验证后,就方便了大家,等同于有了google一个级别的安全,但是我们该怎么使用google authenticator (双重身份验证),  //下面是java的算法,这样大家都可以得到根据key得到公共的秘钥了,直接复制,记得导入JAR包:
//
//commons-codec-1.8.jar
//
//junit-4.10.jar  //测试方法:
//
//1、执行测试代码中的“genSecret”方法,将生成一个KEY(用户为testuser),URL打开是一张二维码图片。
//
//2、在手机中下载“GOOGLE身份验证器”。
//
//3、在身份验证器中配置账户,输入账户名(第一步中的用户testuser)、密钥(第一步生成的KEY),选择基于时间。
//
//4、运行authcode方法将key和要测试的验证码带进去(codes,key),就可以知道是不是正确的秘钥了!返回值布尔  //main我就不写了大家~~因为这个可以当做util工具直接调用就行了
//  package coin.util;  import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;  import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;  import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;  public class GoogleAuthenticator {  // taken from Google pam docs - we probably don't need to mess with these  public static final int SECRET_SIZE = 10;  public static final String SEED = "g8GjEvTbW5oVSV7avLBdwIHqGlUYNzKFI7izOF8GwLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";  public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";  int window_size = 3; // default 3 - max 17 (from google docs)最多可偏移的时间  public void setWindowSize(int s) {  if (s >= 1 && s <= 17)  window_size = s;  }  public static Boolean authcode(String codes, String savedSecret) {  // enter the code shown on device. Edit this and run it fast before the  // code expires!  long code = Long.parseLong(codes);  long t = System.currentTimeMillis();  //獲取系統當前時間GoogleAuthenticator ga = new GoogleAuthenticator();  ga.setWindowSize(15); // should give 5 * 30 seconds of grace...  boolean r = ga.check_code(savedSecret, code, t);  return r;  }  //生成密鑰public static String genSecret() {  String secret = GoogleAuthenticator.generateSecretKey();  GoogleAuthenticator.getQRBarcodeURL("testuser",  "testhost", secret);  return secret;  }  public static void main(String[] args) {String format = "otpauth://totp/ACCOUNT?secret=%s&issuer=%s";String barcodeURL= String.format(format, "PO4F2DIF74VR4ARO", "appname");//生成二维码System.out.println(rwm);}public static String generateSecretKey() {  SecureRandom sr = null;  //SecureRandom是强随机数生成器,主要应用的场景为:用于安全目的的数据数,例如生成秘钥或者会话标示(session ID),在上文《伪随机数安全性》中,已经给大家揭露了弱随机数生成器的安全问题,而使用SecureRandom这样的强随机数生成器将会极大的降低出问题的风险。try {  sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);  //SHA1PRNG随机数算法,主要是實現這個算法SecureRandom rng = SecureRandom.getInstance("SHA1PRNG");sr.setSeed(Base64.decodeBase64(SEED));  byte[] buffer = sr.generateSeed(SECRET_SIZE);  Base32 codec = new Base32();  byte[] bEncodedKey = codec.encode(buffer);  String encodedKey = new String(bEncodedKey);  return encodedKey;  }catch (NoSuchAlgorithmException e) {  // should never occur... configuration error  }  return null;  }  /*** 获取二维码内容URL** @param user   用户* @param host   域* @param secret 密钥* @return 二维码URL*/public static String getQRBarcodeURL(String user, String host, String secret) {  String format = "otpauth://totp/%s?secret=%s&issuer=%s"return String.format(format, user, host, secret);  }  /*** 校验code是否正确** @param secret 密钥* @param code   动态code* @param timeMsec 时间* @return*/public boolean check_code(String secret, long code, long timeMsec) {  Base32 codec = new Base32();  byte[] decodedKey = codec.decode(secret);  //判斷字符串是否一樣。checkCode// convert unix msec time into a 30 second "window"  // this is per the TOTP spec (see the RFC for details)  long t = (timeMsec / 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.  for (int i = -window_size; i <= window_size; ++i) {  long hash;  try {  hash = verify_code(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;  }  }  // The validation code is invalid.  return false;  }  /*** 时间校验密钥与code是否匹配** @param key 解密后的密钥* @param t 时间* @return* @throws NoSuchAlgorithmException* @throws InvalidKeyException*/      private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {  byte[] data = new byte[8];  long value = t;  //“while(i-->0)表示当 i 的值小于或等于0时,退出while循环// 如果写成a>>=b,它可以写成a=a>>b,表示将a的二进制值右移b位后再将值赋给a;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;  // 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 (int) truncatedHash;  }
}  
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;import javax.imageio.ImageIO;import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;/*** @author alexhu** 主要功能:根据二维码内容生成二维码,并保存在指定位置** 依赖:*     <dependency>*       <groupId>com.google.zxing</groupId>*       <artifactId>core</artifactId>*       <version>3.4.1</version>*     </dependency>*/
public class GenerateQRCodeUtils {/*** 二维码颜色*/private static final int BLACK = 0xFF000000;private static final int WHITE = 0xFFFFFFFF;/*** 图片的宽度*/private static int WIDTH = 200;/*** 图片的高度*/private static int HEIGHT = 200;/*** 图片的格式*/private static String FORMAT = "png";/*** 生成二维码** @param basePath 配置文件定义的生成二维码存放文件夹* @param content 二维码内容* @return 文件路径*/public static String generateQRCodeImg(String basePath, String content){try {Map<EncodeHintType, String> encodeMap = new HashMap<EncodeHintType, String>();// 内容编码,生成二维码矩阵encodeMap.put(EncodeHintType.CHARACTER_SET, "utf-8");BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, encodeMap);File file = new File(basePath);if (!file.exists() && !file.isDirectory()){file.mkdirs();}//文件名,默认为时间为名String filePath = basePath + System.currentTimeMillis() + "." + FORMAT;File outputFile = new File(filePath);if (!outputFile.exists()){// 生成二维码文件writeToFile(bitMatrix, FORMAT, outputFile);}return filePath;} catch (Exception e) {e.printStackTrace();}return "";}/*** 把二维码矩阵保存为文件** @param matrix 二维码矩阵* @param format 文件类型,这里为png* @param file  文件句柄* @throws IOException*/public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {BufferedImage image = toBufferedImage(matrix);if (!ImageIO.write(image, format, file)) {throw new IOException("Could not write an image of format " + format + " to " + file);}}    /*** 生成二维码矩阵(内存)** @param matrix 二维码矩阵* @return*/public 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) ? BLACK : WHITE);}}return image;}
}

import org.junit.Test;import static org.example.GenerateQRCodeUtils.generateQRCodeImg;
import static org.example.GoogleAuthenticatorUtils.*;/*** Unit test for Google Authenticator.*/
public class GoogleAuthenticatorTest {/*** Rigorous Test :-)*/@Testpublic void genTest() {/** 注意:先运行前两步,获取密钥和二维码url。 然后只运行第三步,填写需要验证的验证码,和第一步生成的密钥*/String user = "testUser";String host = "test.com";// 第一步:获取密钥String secret = genSecret(user, host);System.out.println("secret:" + secret);// 第二步:根据密钥获取二维码图片url(可忽略)String url = getQRBarcodeURL(user, host, secret);System.out.println("url:" + url);// 第三步 生成二维码generateQRCodeImg("", url);}@Testpublic void verifyTest() {// 第四步:验证(第一个参数是需要验证的验证码,第二个参数是第一步生成的secret运行)boolean result = authcode("105938", "WUH2RO3Q4D53AF5Z");System.out.println("result:" + result);}
}

要验证的验证码,和第一步生成的密钥
*/
String user = “testUser”;
String host = “test.com”;
// 第一步:获取密钥
String secret = genSecret(user, host);
System.out.println(“secret:” + secret);
// 第二步:根据密钥获取二维码图片url(可忽略)
String url = getQRBarcodeURL(user, host, secret);
System.out.println(“url:” + url);
// 第三步 生成二维码
generateQRCodeImg(“”, url);
}

@Test
public void verifyTest() {// 第四步:验证(第一个参数是需要验证的验证码,第二个参数是第一步生成的secret运行)boolean result = authcode("105938", "WUH2RO3Q4D53AF5Z");System.out.println("result:" + result);
}

}


双因子认证系统登录模块相关推荐

  1. 用双因子认证2FA替换Google authenticator谷歌令牌,助力准上市公司实现等保安全审计

    21世纪初,某人力资源科技公司试水HR SaaS赛道,以大客户为目标客群,持续深耕,稳扎稳打,如今已是一家专门为中大型企业提供一体化HR SaaS及人才管理产品/解决方案的头部企业.其产品覆盖了从员工 ...

  2. 企业自研业务系统的登录如何添加动态口令,实施MFA双因子认证?

    一.背景需求 不少企业因业务需要会自己研发业务系统,为保护业务数据安全,首先要确保能访问到业务数据的人员"身份"安全可信. 企业自研业务系统的账号密码基本是 IT 管理员单独管理维 ...

  3. 解读登录双因子认证(MFA)特性背后的TOTP原理

    摘要:随着互联网密码泄露事件频发,越来越多的产品开始支持多因子认证(MFA),TOTP则是MFA领域里最普遍的一种实现方式,本文介绍TOTP的原理和华为云的实践经验. 原理 TOTP(Time-Bas ...

  4. 九州云腾双因素认证系统_“等保2.0”新标准落地 双因子认证(2FA)成标配...

    2019年5月13日,国家标准新闻发布会正式发布网络安全等级保护制度2.0标准(以下简称"等保2.0"),新标准将于2019年12月1日开始实施,这意味着"等保2.0&q ...

  5. SSH 登录添加 Google Authenticator 两步验证 双因子认证

    SSH 登录添加 Google Authenticator 两步验证 双因子认证 安装 Google Authenticator # 时间与客户端进行校验,确保服务器时间与手机时间一致,避免时差导致严 ...

  6. 宁盾目录成功对接Coremail邮箱,为其提供LDAP统一认证和双因子认证

    近日,宁盾与 Coremail 完成兼容适配,在 LDAP 目录用户同步.统一身份认证及双因子认证等模块成功对接.借此机会,双方将加深在产品.解决方案等多个领域的合作,携手共建信创合作生态,打造信创 ...

  7. 等级保护中要求的双因子认证

    在信息安全等级保护三级系统中,在主机安全身份鉴别模块中明确要求,要同时使用两种以上的鉴别技术,也就是业界常说中的双因子认证. 1.什么是双因子认证? 所谓的双因子认证(2FA,two-factor a ...

  8. 将钉钉、飞书、企业微信账号同步到Synology群晖NAS,并开启LDAP双因子认证

    用户使用钉钉.飞书.企业微信账号即可登录群晖 Synology NAS 等LDAP协议应用,账号的创建.删除.变更都会自动同步给下游LDAP应用,这意味着账号管理流程缩短了一倍.而流程如此高效,秘密就 ...

  9. 双因子认证!这4种身份认证方式你都了解吗?

    双因子认证的小科普 前几天公司里忽然出现了一个陌生人,坐在甄梵仁的位子上. 正当我打算和他打个招呼时,他却脱口而出:"贾正经,好久不见!" 这声音不是甄梵仁的吗?他整容了?? 这整 ...

最新文章

  1. 面试宝典:深入理解这110道python面试题,AI和大数据向你招手
  2. 【机器学习入门到精通系列】粒子群优化算法
  3. linspace--创建线性等分向量
  4. 使用vs2015开发linux:Ubuntu程序
  5. HttpClient的释放问题
  6. [SpringSecurity]web权限方案_用户认证_设置用户名密码
  7. 2022年,我只做 3 件事
  8. matplotlib 设置标注方向_Matplotlib绘制带主题及聚类类标的散点图
  9. 网站重构之精简你的代码
  10. 爬虫 selenium
  11. 如何刷新DNS缓存(Windows,Mac,Chrome)
  12. 常用数组方法汇总(ES3、ES5、ES6、ES7、ES10)
  13. 第四周作业上(2014.3.31)
  14. 微信PC版Hook发送群艾特消息研究
  15. 断点续传的原理(转)
  16. 射频和无线技术入门--传统技术--7
  17. matlab标量数据,可视化标量三维体数据的方法
  18. 数据驱动运营,为门店开拓第二增长曲线。
  19. 20万奖补|成都两化融合管理贯标项目拟支持名单发布|
  20. 场景化分析rem布局原理

热门文章

  1. 点云数据bin格式转换为pcd格式的方法
  2. Fomo3D系统源码讲解
  3. WEB前端学习路线2023完整版(附视频教程+学习资料)
  4. 计算机网络思科模拟器的拓朴结构以及交换机的原理与配置
  5. 艾媒:网易考拉正品信任度位居跨境电商行业之首
  6. 12306是如何实现高流量高并发的关键技术
  7. 计算机组成原理唐朔飞高分笔记,计算机组成原理唐朔飞高分笔记【参考】.doc...
  8. 「SymPy」符号运算(4) 微积分与有限差分
  9. mybaties日常开发总结
  10. npm ERR! Invalid tag name “@vue-cli“: Tags may not have any characters that encodeURIComponent encod