基于Google 验证器 实现内网的双因素认证

最近的一个项目需要实现双因素强认证,平常我们都是采用 静态密码+动态短信这样的方式来实现,但用户侧并没有相应的短信接口。 后来决定采用 google身份验证器来实现。在网上找了一些资料和代码片段,经过梳理和改造,目前已上线使用了,效果还是比较好的,记录一下,也给需要的朋友做个参考。

首先简述一下双因素认证:双因素身份认证就是通过你所知道再加上你所能拥有的这二个要素组合到一起才能发挥作用的身份认证系统。双因素认证是一种采用时间同步技术的系统,采用了基于时间、事件和密钥三变量而产生的一次性密码来代替传统的静态密码。每个动态密码卡都有一个唯一的密钥,该密钥同时存放在服务器端,每次认证时动态密码卡与服务器分别根据同样的密钥,同样的随机参数(时间、事件)和同样的算法计算了认证的动态密码,从而确保密码的一致性,从而实现了用户的认证。----引自百度百科

实现原理可以参考:http://www.zhihu.com/question/20462696                            http://blog.seetee.me/archives/73.html

主要实现思路:

1: 当用户 登录系统,先经过用户名和密码验证后,再进行动态验证,查询用户的Google密钥是否启用(密钥信息在数据库中保存);

2:如果没有Google密钥,则将页面重定向到启用密钥的页面;

2.1  : 调用 GoogleAuthenticator生成google密钥,并将密钥写入二维码中,提示用户使用Google验证器扫描二维码;

2.2  : 用户使用Google身份验证器扫描二维码,用户的手机中即可生成动态验证码;

2.3  : 为了确保二维码密钥和用户手机上时间与服务器基本一致,需要让用户验证生成的动态验证码是否可以通过系统的验证(验证过程同登录过程,暂且不表);

3:如果用户已有Google密钥,用户打开手机上的Google身份验证器,找到对应的帐号生成的动态码,输入系统;

4: 调用 GoogleAuthenticator 密钥验证接口,判断是否验证通过;

4.1: 验证通过,执行系统登录的相关流程;

4.2: 验证不通过,提示用户:查验手机时间 与服务器的时间是否一致或者帐号与动态验证码的对应关系是否准确(如果用户手机上有多个帐号时,经常出现这种问题)

待完善功能:如果用户手机丢失,可信用户重置google密钥;

以上,仅是我的一个实现思路,如果大家有好的想法,欢迎交流。

以下为两个重要类的代码,供参考。

如果需要完整的项目代码,请移步:基于Google 验证器 实现内网的双因素认证项目   ,由于本代码为实际项目的代码片段,经过删减后的版本,比较混乱(但可以正常运行),所以不建议大家下载,

谷歌身份验证的Java服务器端:

package com.google.module.authenticator.utils;

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;

import com.google.framework.QuickResponse.QRUtil;

/**
 * 谷歌身份验证的Java服务器端
 */
public class GoogleAuthenticator {

    // 来自谷歌文档,不用修该
    public static final int SECRET_SIZE = 10;

    public static final String SEED = "g8GjEvTbW5oVSV7avLBdwIHqGlUYNzKFI7izOF8GwLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";

    public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";//安全哈希算法(Secure Hash Algorithm)

    int window_size = 3; //默认 3 - 最大值17 (from google docs)多可偏移的时间--3*30秒的验证时间(手机客户端验证为30秒变化次)

    /**
     * 设置偏移量,最大值17
     * 默认 3 - 最大值17 (from google docs)多可偏移的时间--3*30秒的验证时间(手机客户端验证为30秒变化次)
     * @param s
     */
    public void setWindowSize(int s) {
        if (s >= 1 && s <= 17)
            window_size = s;
    }

    /**秘钥
     * 随机生成1个秘钥,这个秘钥必须在服务器上保存,用户在手机Google身份验证器上配置账号时也要这个秘钥
     * @return secret key
     */
    public static String generateSecretKey() {
        SecureRandom sr = null;
        try {
            sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
            byte[] seedBytes = SEED.getBytes();
            sr.setSeed(Base64.decodeBase64(seedBytes));
            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生成并显示二维码。用户扫描这个二维码
     *谷歌身份验证应用程序的智能手机注册身份验证代码
     *他们还可以手动输入秘钥key
     * @param user
     * @param host
     * @param secret 之前为用户生成的秘钥
     * @return 二维码的url
     */
    /**
     * @Definition:
     * @author: TangWenWu
     * @Created date: 2014-11-24
     * @param user
     * @param host
     * @param secret
     * @return
     */
    public static String getQRBarcodeURL(String user, String host, String secret) {
	    /*
	     *   由于是内网系统,无法访问google,所以注释
	     *    String format = "https://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otpauth://totp/%[email protected]%s%%3Fsecret%%3D%s";
	     *    return String.format(format, user, host, secret);
	     */
	    //采用com.google.zxing 自己生成 二维码图片,保存在项目某个目录下
		// 二维码内容
		String content = "otpauth://totp/"+user+"@"+host+"?secret="+secret;
		// 二维码宽度
		int width = 300;
		// 二维码高度
		int height = 300;
		// 二维码存放地址
		String imageName = "googleAuthCode_"+user+"_"+host+".png";
		return QRUtil.generateImageInBorderQR(content, width, height, imageName);

    }

    public static void main(String[] args) {
		String path = getQRBarcodeURL("tangww","ROOT","WERRWEIU23424U");
		System.out.println("path======================="+path);
	}

    /**
     * 查用户输入的6位码是否有效
     * @param secret 秘钥
     * @param code 6位码
     * @param t 偏移时间
     * @return
     */
    public boolean check_code(String secret, long code, long timeMsec) {
        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 = (timeMsec / 1000L) / 30L;
        // window是用来检验之前生成的6位码
        // 可以用这个window_size来调整允许6位码生效的时间
        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;
    }
    /**
     * 生成验证码
     * @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;
        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;
    }
}

生成二维码密钥类

package com.google.framework.QuickResponse;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Hashtable;

import javax.imageio.ImageIO;

import com.google.framework.utils.FilePathUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
 * @Description:二维码图片工具类                使用了两张工具图    名称是 heihei.png  qr_bg.png
 * @Copyright (C) 2014 BOCO All Right Reserved.
 * @createDate:2014-11-24
 * @author:TangWenWu
 * @version 1.0
 *
 *
 *
 *
 */
public class QRUtil {
	/**
	 * 编码(将文本生成二维码)
	 *
	 * @param content
	 *            二维码中的内容
	 * @param width
	 *            二维码图片宽度
	 * @param height
	 *            二维码图片高度
	 * @param imagePath
	 *            二维码图片存放位置
	 * @return 图片地址
	 */
	private static String encode(String content, int width, int height,
			String imagePath) {
		Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
		// 设置编码类型为utf-8
		hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
		// 设置二维码纠错能力级别为H(最高)
		hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
		BitMatrix byteMatrix = null;
		try {
			// 生成二维码
			byteMatrix = new MultiFormatWriter().encode(content,
					BarcodeFormat.QR_CODE, width, height, hints);
			File file = new File(imagePath);
			MatrixToImageWriter.writeToFile(byteMatrix, "png", file);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (WriterException e) {
			e.printStackTrace();
		}

		return imagePath;
	}

	/**
	 * 解码(读取二维码图片中的文本信息)
	 *
	 * @param imagePath
	 *            二维码图片路径
	 * @return 文本信息
	 */
	private static String decode(String imagePath) {
		// 返回的文本信息
		String content = "";
		try {
			// 创建图片文件
			File file = new File(imagePath);
			if (!file.exists()) {
				return content;
			}
			BufferedImage image = null;
			image = ImageIO.read(file);
			if (null == image) {
				return content;
			}
			// 解码
			LuminanceSource source = new BufferedImageLuminanceSource(image);
			BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
			Hashtable hints = new Hashtable();
			hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
			Result rs = new MultiFormatReader().decode(bitmap, hints);
			content = rs.getText();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ReaderException e) {
			e.printStackTrace();
		}
		return content;
	}

	/**
	 * 图片打水印
	 *
	 * @param bgImage
	 *            背景图
	 * @param waterImg
	 *            水印图
	 * @param uniqueFlag
	 *            生成的新图片名称中的唯一标识,用来保证生成的图片名称不重复,如果为空或为null,将使用当前时间作为标识
	 * @return 新图片路径
	 */
	private static String addImageWater(String bgImage, String waterImg,
			String uniqueFlag) {
		int x = 0;
		int y = 0;
		String newImgPath = "";

		if (null == uniqueFlag) {
			uniqueFlag = new SimpleDateFormat("yyyyMMddHHmmss")
					.format(new Date());
		} else if (uniqueFlag.trim().length() < 1) {
			uniqueFlag = new SimpleDateFormat("yyyyMMddHHmmss")
					.format(new Date());
		}

		try {
			File file = new File(bgImage);
			String fileName = file.getName();
			Image image = ImageIO.read(file);
			int width = image.getWidth(null);
			int height = image.getHeight(null);
			BufferedImage bufferedImage = new BufferedImage(width, height,
					BufferedImage.TYPE_INT_RGB);
			Graphics2D g = bufferedImage.createGraphics();
			g.drawImage(image, 0, 0, width, height, null);
			Image waterImage = ImageIO.read(new File(waterImg)); // 水印文件
			int width_water = waterImage.getWidth(null);
			int height_water = waterImage.getHeight(null);
			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
					1));
			int widthDiff = width - width_water;
			int heightDiff = height - height_water;
			x = widthDiff / 2;
			y = heightDiff / 2;

			g.drawImage(waterImage, x, y, width_water, height_water, null); // 水印文件结束
			g.dispose();

			if (bgImage.contains(fileName)) {
				newImgPath = bgImage.replace(fileName, uniqueFlag + fileName);
			}
			File newImg = new File(newImgPath);
			ImageIO.write(bufferedImage, "png", newImg);

			File waterFile = new File(waterImg);

			if (file.exists()) {
				file.delete();
			}

			if (waterFile.exists()) {
				waterFile.delete();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return newImgPath;
	}

	/**
	 * 图片缩放
	 *
	 * @param filePath
	 *            图片路径
	 * @param height
	 *            缩放到高度
	 * @param width
	 *            缩放宽度
	 * @param fill
	 *            比例足时是否填白 true为填白,二维码是黑白色,这里调用时建议设为true
	 * @return 新图片路径
	 */
	private static String resizeImg(String filePath, int width, int height,
			boolean fill) {

		String newImgPath = "";

		try {
			double ratio = 0; // 缩放比例
			File f = new File(filePath);
			String fileName = f.getName();
			BufferedImage bi = ImageIO.read(f);
			Image itemp = bi.getScaledInstance(width, height,
					BufferedImage.SCALE_SMOOTH);

			if (height != 0 && width != 0) {
				// 计算比例
				if ((bi.getHeight() > height) || (bi.getWidth() > width)) {
					if (bi.getHeight() > bi.getWidth()) {
						ratio = (new Integer(height)).doubleValue()
								/ bi.getHeight();
					} else {
						ratio = (new Integer(width)).doubleValue()
								/ bi.getWidth();
					}
					AffineTransformOp op = new AffineTransformOp(
							AffineTransform.getScaleInstance(ratio, ratio),
							null);
					itemp = op.filter(bi, null);
				}
			}

			if (fill) {
				BufferedImage image = new BufferedImage(width, height,
						BufferedImage.TYPE_INT_RGB);
				Graphics2D g = image.createGraphics();
				g.setColor(Color.white);
				g.fillRect(0, 0, width, height);
				if (width == itemp.getWidth(null)) {
					g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2,
							itemp.getWidth(null), itemp.getHeight(null),
							Color.white, null);
				} else {
					g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0,
							itemp.getWidth(null), itemp.getHeight(null),
							Color.white, null);
				}
				g.dispose();
				itemp = image;
			}
			String now = new SimpleDateFormat("yyyyMMddHHmmss")
					.format(new Date());
			if (filePath.contains(fileName)) {
				newImgPath = filePath.replace(fileName, now + fileName);
			}

			File newImg = new File(newImgPath);
			ImageIO.write((BufferedImage) itemp, "png", newImg);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return newImgPath;
	}

	/**
	 * 图片添加边框
	 *
	 * @param mainImgPath
	 *            要加边框的图片
	 * @param bgImgPath
	 *            背景图(实际上是将图片放在背景图上,只利用背景图的边框效果)
	 * @return 制作完成的图片路径
	 */
	private static String addWaterBorder(String mainImgPath, String bgImgPath) {

		String borderImgPath = "";

		try {
			File f = new File(mainImgPath);

			BufferedImage bi;

			bi = ImageIO.read(f);

			// 背景图长宽都比主图多4像素,这是因为我画的背景图的边框效果的大小正好是4像素,
			// 主图周边比背景图少4像素正好能把背景图的边框效果完美显示出来
			int width = bi.getWidth();
			int height = bi.getHeight();

			int bgWidth = width + 4;
			int bgHeight = height + 4;

			String now = new SimpleDateFormat("yyyyMMddHHmmss")
					.format(new Date());
			borderImgPath = QRUtil.addImageWater(QRUtil.resizeImg(bgImgPath,
					bgHeight, bgWidth, true), mainImgPath, now);

			if (f.exists()) {
				f.delete();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return borderImgPath;
	}

	/**
	 * @Definition: 生成常规二维码
	 * @author: TangWenWu
	 * @Created date: 2014-11-25
	 * @param content  二维码中的内容
	 * @param width    二维码宽度  单位:像素  建议300
	 * @param height   二维码高度  单位:像素  建议300
	 * @param imageName  二维码图片名称     生成路径为:appName/WebRoot/指定的目录/imageName
	 * @return
	 */
	public static String generateCommonQR(String content,int width,int height,String imageName){
		String appPath = FilePathUtil.getResourcePath();
		/** 部分一开始***********生成常规二维码 *************/
		// 二维码存放地址
		imageName = appPath+"templates/qr/"+imageName;
		// 生成二维码,返回的是生成好的二维码图片的所在路径
		String qrImgPath = QRUtil.encode(content, width, height, imageName);
		/** 部分一结束***********如果生成不带图片的二维码,到这步已经完成了 *************/
		return qrImgPath;
	}

	/**
	 * @Definition: 生成带图片但图片不带边框的二维码
	 * @author: TangWenWu
	 * @Created date: 2014-11-25
	 * @param content  二维码中的内容
	 * @param width    二维码宽度  单位:像素  建议300
	 * @param height   二维码高度  单位:像素  建议300
	 * @param imageName  二维码图片名称     生成路径为:appName/WebRoot/指定的目录/imageName
	 * @return
	 */
	public static String generateOnlyImageQR(String content,int width,int height,String imageName){
		String appPath = FilePathUtil.getResourcePath();
		String qrImgPath = generateCommonQR(content,width,height,imageName);
		// 缩放水印图片,为保证二维码的读取正确,图片不超过二维码图片的五分之一,这里设为六分之一
		String waterImgPath = QRUtil.resizeImg(appPath+"images/boco_big.png", width/6,height/6, true);

		 //生成带有图片的二维码,返回的是生成好的二维码图片的所在路径
		String qrImage = QRUtil.addImageWater(qrImgPath,waterImgPath,"BOCO");
		return qrImage;
	}

	/**
	 * @Definition: 生成带图片且图片带边框的二维码
	 * @author: TangWenWu
	 * @Created date: 2014-11-25
	 * @param content  二维码中的内容
	 * @param width    二维码宽度  单位:像素  建议300
	 * @param height   二维码高度  单位:像素  建议300
	 * @param imageName  二维码图片名称     生成路径为:appName/WebRoot/指定的目录/imageName
	 * @return
	 */
	public static String generateImageInBorderQR(String content,int width,int height,String imageName){
		String appPath = FilePathUtil.getResourcePath();
		String qrImgPath = generateCommonQR(content,width,height,imageName);
		// 缩放水印图片,为保证二维码的读取正确,图片不超过二维码图片的五分之一,这里设为六分之一
		// d:/qr/heihei.png 这图片是要加在二维码中间的那张图
		String waterImgPath = QRUtil.resizeImg(appPath+"images/boco_big.png", width / 6,
				height / 6, true);

		// d:/qr/qr_bg.png这种图片是自己画好边框光晕效果的边框底图
		String tempImg = QRUtil.addWaterBorder(waterImgPath, appPath+"images/qr_bg.png");

		// 生成带有边框图片的二维码,返回的是生成好的二维码图片的所在路径
		String qrImage = QRUtil.addImageWater(qrImgPath, tempImg, "BOCO");
		return qrImage;
	}

	public static void main(String[] args) {
		String path = FilePathUtil.getResourcePath();
		System.out.println(path);
		/** 部分一开始***********生成常规二维码 *************/
		// 二维码内容
		String content = "http://blog.csdn.net/tangwwk";
		// 二维码宽度
		int width = 300;
		// 二维码高度
		int height = 300;
		// 二维码存放地址
		String imagePath = path+"templates/qr/"+"Site.png";
		// 生成二维码,返回的是生成好的二维码图片的所在路径
		String qrImgPath = QRUtil.encode(content, width, height, imagePath);
		/** 部分一结束***********如果生成不带图片的二维码,到这步已经完成了 *************/

		/** 部分二开始***********如果生成带图片但图片不带边框的二维码,解开这部分注释 *************/

		// 缩放水印图片,为保证二维码的读取正确,图片不超过二维码图片的五分之一,这里设为六分之一
		// String waterImgPath = QRUtil.resizeImg("d:/qr/heihei.jpg", width/6,
		// height/6, true);
		//
		// //生成带有图片的二维码,返回的是生成好的二维码图片的所在路径
		// String qrImage = QRUtil.addImageWater(qrImgPath,
		// waterImgPath,"thatway");
		/** 部分二结束***********如果生成带图片但图片不带边框的二维码,解开这部分注释 *************/

		/** 部分三开始(部分三不能和部分二共存)***********如果生成带图片且图片带边框的二维码,解开这部分注释 ****/

		// 缩放水印图片,为保证二维码的读取正确,图片不超过二维码图片的五分之一,这里设为六分之一
		// d:/qr/heihei.png 这图片是要加在二维码中间的那张图
		String waterImgPath = QRUtil.resizeImg(path+"images/boco_big.png", width / 6,
				height / 6, true);

		// d:/qr/qr_bg.png这种图片是自己画好边框光晕效果的边框底图
		String tempImg = QRUtil.addWaterBorder(waterImgPath, path+"images/qr_bg.png");

		// 生成带有边框图片的二维码,返回的是生成好的二维码图片的所在路径
		String qrImage = QRUtil.addImageWater(qrImgPath, tempImg, "tangwwk");
		/** 部分三结束***********如果生成带图片且图片带边框的二维码,解开这部分注释 *************/

		/******* 测试一下解码 ******/
		System.out.println(QRUtil.decode(qrImage));
		;

	}
}
时间: 2024-10-25 01:50:09

基于Google 验证器 实现内网的双因素认证的相关文章

在线讲堂:轻松搞定基于嵌入式、桌面、内网和云的软件授权

我们诚挚地邀请您参加 6 月19 日举办的圣天诺第2季 "在线讲堂".软件交付的规划过程是一个连续组合规划过程 - 开发.授权.部署和支持.反馈.不懈努力研发客户期盼的产品,同时让服务供应商获利. 本期讲堂帮助您了解: 追踪软件部署如何改善客户体验和提高客户满意度 授权实施选项 – 从桌面.嵌入式.内网,到移动和云 探讨内网环境的软件授权 -   在内网环境下,强制执行和授权面临哪些挑战? -   内网环境下成功部署的解决方案是什么? 用一套方案管理所有授权部署的重要性 主讲:王福涛

Linux 之 利用Google Authenticator实现用户双因素认证

一.介绍:什么是双因素认证 双因素身份认证就是通过你所知道再加上你所能拥有的这二个要素组合到一起才能发挥作用的身份认证系统.双因素认证是一种采用时间同步技术的系统,采用了基于时间.事件和密钥三变量而产生的一次性密码来代替传统的静态密码.每个动态密码卡都有一个唯一的密钥,该密钥同时存放在服务器端,每次认证时动态密码卡与服务器分别根据同样的密钥,同样的随机参数(时间.事件)和同样的算法计算了认证的动态密码,从而确保密码的一致性,从而实现了用户的认证. 说白了,就像我们几年前去银行办卡送的口令牌,以及

Linux 利用Google Authenticator实现ssh登录双因素认证

1.介绍 双因素认证:双因素身份认证就是通过你所知道再加上你所能拥有的这二个要素组合到一起才能发挥作用的身份认证系统.双因素认证是一种采用时间同步技术的系统,采用了基于时间.事件和密钥三变量而产生的一次性密码来代替传统的静态密码.每个动态密码卡都有一个唯一的密钥,该密钥同时存放在服务器端,每次认证时动态密码卡与服务器分别根据同样的密钥,同样的随机参数(时间.事件)和同样的算法计算了认证的动态密码,从而确保密码的一致性,从而实现了用户的认证.因每次认证时的随机参数不同,所以每次产生的动态密码也不同

linux服务器外网内网(双网络)搭建

一共有2台服务器,分别用a,b表示.a双网卡,即有外网也有内网.b只有内网环境.a,b的内网是通过交换机组建.至于外网怎么搭建我就不说了.关键说一说内网是怎么组建的. 如果你对linux不熟悉,对网卡配置文件不熟悉的话,可能会踩到不少坑.不过你也可能会很顺利的就把这个内网组建起来.下面说说步骤: 1.a,b分别用网线与交换机连起来. 2.a,b的内网ip都设成同一个号段(例如 : a:192.168.1.2  b:192.168.1.3 ) 完成以上步骤然后互ping一下,如果通了就说明你的内网

【Linux】使用Google Authenticator 实现ssh登录双因素认证

一般来说,使用ssh远程登录服务器,只需要输入账号和密码,显然这种方式不是很安全.为了安全着想,可以使用GoogleAuthenticator(谷歌身份验证器),以便在账号和密码之间再增加一个验证码,只有输入正确的验证码之后,再输入密码才能登录.这样就增强了ssh登录的安全性.账号.验证码.密码三者缺一个都不能登录,即使账号和密码正确,验证码错误,同样登录失败.其中,验证码是动态验证码,并且是通过手机客户端自动获取(默认每隔30秒失效一次) Google Authenticator开源版主页 h

搭建基于HTTP协议内网yum仓库

目录 1. 前言 2. 把rpm包下载到本地 3. 配置nginx对外提供服务 4. 配置本地repo文件 5. 生成repodata信息 6. 检查及使用 7. 对管理机器上的仓库进行更新 参考资料 环境:VMware-Workstation-12-Pro,Windows-10,CentOS-7.5,Xshell5 1. 前言 如果我们的yum仓库需要多台机器共同使用,此时把yum仓库做成本地的,然后一台台scp推送过去比较麻烦,此时可以考虑搭建一个基于HTTP协议,供给内网其它机器使用的本地

新花生壳+tomcat(内网映射,无需设置路由器)建站攻略

说明: 1.适用于内网用户(局域网,校园网,或者公司网等无法更改路由器映射的情况) 2.一共花了8块钱…………心疼.不过如果大家有钱的话,8块钱,少吃一顿麻辣烫就好了~总之,这个适用于测试网站,小访问量的应用,仅供娱乐用.因为要用自己电脑做服务器,对电脑损耗很大,所以正式应用或商业应用时需要去专业提供服务器的公司如阿里云服务. 步骤: 1.下载新花生壳,并申请域名.具体下载方法和安装方法请自行百度.最近花生壳域名注册不是免费的了,不过新用户可以选择免费送一个域名,如下: 用户名为这个:就不暴露自

内网准入解决方案

内网准入解决方案 网络准入控制系统,主要功能是通过客户端设置可信网络,该可信网络内部互信计算机间可以正常相互访问,非法计算机未经授权不能接入可信网络.可信网络内部终端也不能非法外联非可信网络,通过本系统可以低成本实现内外网软件隔离和终端信息安全防护,对于已经实施内外网物理隔离单位还可以作为终端信息防护的解决方案,防止终端因为非法接入.外联导致泄密,网络准入系统,主要功能是通过软件方式设置可信网络,是以终端准入控制的一种内网准入控制软件,相比其他内网准入控制系统更安全,更可靠,更容易部署. 认证数

IE8 内网环境下载文件到99%卡住的问题解决

内网是不能连接互联网的,也不用设置DNS.一些电脑安装了360安全卫士,有时候会修复DNS,导致DNS也被设置,如果IE开启了SmartScreen筛选器,内网的网站下载文件会出现卡住的现象. 下面是IE8关闭smartscreen筛选器的注册表文本,导入注册表后就可以修复内网下载卡住的问题. Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Phishing