1. 程式人生 > >驗證碼原理詳解與案例

驗證碼原理詳解與案例

剩下的細節問題還有:

1,驗證碼如何加噪成圖片

2,服務端如何維護驗證碼

案例程式碼在:https://github.com/yejingtao/forblog/tree/master/demo-securityCode

核心程式碼詳解:

前端:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Create user </title>
    </head>
    <body>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="name"/> </label></div>
            <div><label> User Password : <input type="password" name="password"/> </label></div>
            <img src="/security" onclick="refreshSecurityCode(this);" />
            	 
            <input name="securityCode" 	size="8" />
            <div><input type="submit" value="Login"/></div>
        </form>
    </body>
    
<script>
function refreshSecurityCode(obj) {
        obj.src = "/security?_t=" + Math.random();
    }
</script>

</html>

驗證碼code生成:原理很簡單,就是隨機字串

public class SecurityCodeUtil {
	/**
	 * 驗證碼難度級別,Simple只包含數字,Medium包含數字和小寫英文,MediumPlus包含大小英文,Hard包含數字和大小寫英文
	 */
	public enum SecurityCodeLevel {
		Simple, Medium, MediumPlus, Hard
	};

	/**
	 * 產生預設驗證碼,4位中等難度
	 * 
	 * @return String 驗證碼
	 */
	public static String getSecurityCode() {
		return getSecurityCode(4, SecurityCodeLevel.MediumPlus, false);
	}

	/**
	 * 產生長度和難度任意的驗證碼
	 * 
	 * @param length
	 *            長度
	 * @param level
	 *            難度級別
	 * @param isCanRepeat
	 *            是否能夠出現重複的字元,如果為true,則可能出現 5578這樣包含兩個5,如果為false,則不可能出現這種情況
	 * @return String 驗證碼
	 */
	public static String getSecurityCode(int length, SecurityCodeLevel level, boolean isCanRepeat) {
		// 隨機抽取len個字元
		int len = length;

		// 字元集合(除去易混淆的數字0、數字1、字母l、字母o、字母O)
		char[] codes = { '1', '2', '3', '4', '5', '6', '7', '8', '9', //
				'a', 'b', 'c', 'd', 'e', 'f', 'g', //
				'h', 'i', 'j', 'k', 'm', 'n', //
				'p', 'q', 'r', 's', 't', //
				'u', 'v', 'w', 'x', 'y', 'z', //
				'A', 'B', 'C', 'D', 'E', 'F', 'G', //
				'H', 'I', 'J', 'K', 'L', 'M', 'N', //
				'P', 'Q', 'R', 'S', 'T', //
				'U', 'V', 'W', 'X', 'Y', 'Z' };

		// 根據不同的難度擷取字元陣列
		if (level == SecurityCodeLevel.Simple) {
			codes = ArrayUtils.copyOfRange(codes, 0, 9);
		} else if (level == SecurityCodeLevel.Medium) {
			codes = ArrayUtils.copyOfRange(codes, 0, 33);
		} else if (level == SecurityCodeLevel.MediumPlus) {
			codes = ArrayUtils.copyOfRange(codes, 34, codes.length);
		}
		// 字元集合長度
		int n = codes.length;

		// 丟擲執行時異常
		if (len > n && isCanRepeat == false) {
			throw new RuntimeException(String.format("呼叫SecurityCode.getSecurityCode(%1$s,%2$s,%3$s)出現異常," //
					+ "當isCanRepeat為%3$s時,傳入引數%1$s不能大於%4$s", len, level, isCanRepeat, n));
		}
		// 存放抽取出來的字元
		char[] result = new char[len];
		// 判斷能否出現重複的字元
		if (isCanRepeat) {
			for (int i = 0; i < result.length; i++) {
				// 索引 0 and n-1
				int r = (int) (Math.random() * n);

				// 將result中的第i個元素設定為codes[r]存放的數值
				result[i] = codes[r];
			}
		} else {
			for (int i = 0; i < result.length; i++) {
				// 索引 0 and n-1
				int r = (int) (Math.random() * n);

				// 將result中的第i個元素設定為codes[r]存放的數值
				result[i] = codes[r];

				// 必須確保不會再次抽取到那個字元,因為所有抽取的字元必須不相同。
				// 因此,這裡用陣列中的最後一個字元改寫codes[r],並將n減1
				codes[r] = codes[n - 1];
				n--;
			}
		}
		return String.valueOf(result);
	}
}

前端技術很容易獲取文字版的驗證碼,所以要以二進位制流的形式返回加噪後的驗證碼,主要靠java.awt裡的包:

public class SecurityImageSupport {

	/**
	 * 返回驗證碼圖片的流格式
	 * 
	 * @param securityCode
	 *            驗證碼
	 * @return ByteArrayInputStream 圖片流
	 */
	public static ByteArrayInputStream getImageAsInputStream(String securityCode) {
		BufferedImage image = createImage(securityCode);
		return convertImageToStream(image);
	}
	
	public static byte[] getImageAsByte(String securityCode) {
		BufferedImage image = createImage(securityCode);
		return convertImageToByte(image);
	}

	/**
	 * 生成驗證碼圖片
	 * 
	 * @param securityCode
	 *            驗證碼字元
	 * @return BufferedImage 圖片
	 */
	private static BufferedImage createImage(String securityCode) {
		// 驗證碼長度
		int codeLength = securityCode.length();
		// 字型大小
		int fSize = 13;
		int fWidth = fSize + 1;
		// 圖片寬度
		int width = codeLength * fWidth + 15;
		// 圖片高度
		int height = (int) (fSize * 1.5) + 1;
		// 圖片
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics2D g = image.createGraphics();
		Color bgColor = new Color(239, 241, 249);
		// 設定背景色
		g.setColor(bgColor);
		// 填充背景
		g.fillRect(0, 0, width, height);
		// 設定邊框顏色
		g.setColor(bgColor);
		// 邊框字型樣式
		g.setFont(new Font("Arial", Font.BOLD, height - 2));
		// 繪製邊框
		g.drawRect(10, 10, width - 1, height - 1);
		// 繪製噪點
		Random rand = new Random();
		// 設定噪點顏色
		g.setColor(Color.LIGHT_GRAY);
		for (int i = 0; i < codeLength * 6; i++) {
			int x = rand.nextInt(width);
			int y = rand.nextInt(height);
			// 繪製1*1大小的矩形
			g.drawRect(x, y, 1, 1);
		}
		// 繪製驗證碼
		int codeY = height - 5;
		// 設定字型顏色和樣式
		g.setColor(new Color(80, 25, 28));
		g.setFont(new Font("Georgia", Font.BOLD | Font.ITALIC, fSize));
		for (int i = 0; i < codeLength; i++) {
			g.drawString(String.valueOf(securityCode.charAt(i)), i * 16 + 5, codeY);
		}

		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		Random r = new Random();
		CubicCurve2D cubic = new CubicCurve2D.Float(2, height / 2 + r.nextInt(8) - 4, //
				2 + width * 1 / 3, height / 2 + r.nextInt(8) - 4, //
				2 + width * 2 / 3, height / 2 + r.nextInt(8) - 4, //
				width - 2, height / 2 + r.nextInt(8) - 4);
		g.draw(cubic);

		// 關閉資源
		g.dispose();
		return image;
	}

	/**
	 * 將BufferedImage轉換成ByteArrayInputStream
	 * 
	 * @param image
	 *            圖片
	 * @return ByteArrayInputStream 流
	 */
	private static ByteArrayInputStream convertImageToStream(BufferedImage image) {
		byte[] bts = convertImageToByte(image);
		if(bts!=null) {
			return new ByteArrayInputStream(bts);
		}else {
			return null;
		}
	}
	
	private static byte[] convertImageToByte(BufferedImage image) {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			ImageIO.write(image, "jpeg", bos);
			image.flush();
			byte[] bts = bos.toByteArray();
			return bts;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

}

與Redis互動:

@Service
public class SecurityCacheServiceImpl implements SecurityCacheService{
	
	public static final String REDIS_KEY = "sessionMap";
	
	@Autowired
	private RedisTemplate<String,String> redisTemplate;
	
	@Override
	public void setCodeCache(String sessionID, String securityCode) {
		HashOperations<String, String, String> hashOp = redisTemplate.opsForHash();
		hashOp.put(REDIS_KEY,sessionID,securityCode);
	}

	@Override
	public String getCodeCache(String sessionID) {
		HashOperations<String, String, String> hashOp = redisTemplate.opsForHash();
		return hashOp.get(REDIS_KEY, sessionID);
	}

}

Controller在生成驗證碼之前需要維護下Redis快取:

@RequestMapping("/security")
	public ResponseEntity<byte[]> securityCode(HttpServletRequest httpRequest) {
		//獲取驗證碼文字
		String securityCode = SecurityCodeUtil.getSecurityCode();
		//Redis快取驗證碼資訊
		securityCacheService.setCodeCache(getSessionId(httpRequest), securityCode);
		byte[] bytes = SecurityImageSupport.getImageAsByte(securityCode);
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.IMAGE_JPEG);
		return new ResponseEntity<byte[]>(bytes, headers,HttpStatus.OK);
	}

驗證碼效果圖:






相關推薦

驗證原理案例

剩下的細節問題還有: 1,驗證碼如何加噪成圖片 2,服務端如何維護驗證碼 案例程式碼在:https://github.com/yejingtao/forblog/tree/master/demo-securityCode 核心程式碼詳解: 前端: <!DOCTYPE html> <ht

ConstraintLayout案例

我們在使用android studio的時候,發現Mainactivity的預設佈局從RelativeLayout變成了ConstraintLayout。什麼是ConstraintLayout呢?Constraint Layout是Google在2016年的Google I/O大會上提出

Skip List(跳躍表)原理實現

#include<stdio.h>  #include<stdlib.h>      #define MAX_LEVEL 10 //最大層數      //節點  typedef  struct nodeStructure  {      int key;      int value

隨機森林 演算法原理實現步驟

#include <cv.h> // opencv general include file #include <ml.h> // opencv machine learning include file #include <stdio.h>

對《軟體效能測試過程案例剖析》的看法

  這本書我看了有一段時間了,最近一直比較忙,就什麼也沒寫。 今天想來想去還是寫寫自己的感觸: 一直不太喜歡,有些沒有任何的分析就誇書寫的好,或者罵書寫的不好之類的人。 這本書我看了兩遍的。為什麼書我看了兩遍才會想寫點東西呢。我認為不管什麼書,看第一遍總是在受著作者的影響,每

PHP實現的封裝驗證

用PHP寫一個驗證碼類,並進行封裝。類名: validationcode.class.php程式碼如下: 程式碼如下: <?php  class ValidationCode {   private $width;   private $height;   private $codeNum;   pri

EM演算法原理高斯混合模型

藉助於machine learning cs229和文章【1】中的內容把EM演算法的過程順一遍,加深一下印象。 關於EM公式的推導,一般會有兩個證明,一個是利用Jesen不等式,另一個是將其分解成KL距離和L函式,本質是類似的。 下面介紹Jensen EM的

HashMap HashSet 原理 對比

Hashmap 1.原理 陣列連結結構,put原理:首先計算key的hash值,找到table陣列中的位置,找到table[i]位置後,這個連結串列找是否有這個key值,使用equal()方法進行比較,有這個key值,那麼進行更改value操作,沒有

Android端web端以及資料庫的建立它們之間的互動案例分析

本專案是筆者利用十天的實訓時間完成,之間掌握並學到了不少關於Android和web方面的知識,比如:json的資料交換,ListViewAdapter介面卡的使用以及網路請求web端互動的問題。趁熱打

SVM-支援向量機原理實踐之一

SVM-支援向量機原理詳解與實踐 前言 去年由於工作專案的需要實際運用到了SVM和ANN演算法,也就是支援向量機和人工神經網路演算法,主要是實現專案中的實時採集圖片(工業高速攝像頭採集)的影象識別的這一部分功能,雖然幾經波折,但是還好最終還算順利完成了專案的任務,忙碌一年

資料庫連線池原理自定義連線池實現

實現原理資料庫連線池在初始化時將建立一定數量的資料庫連線放到連線池中,這些資料庫連線的數量是由最小資料庫連線數制約。無論這些資料庫連線是否被使用,連線池都將一直保證至少擁有這麼多的連線數量。連線池的最大資料庫連線數量限定了這個連線池能佔有的最大連線數,當應用程式向連線池請求的連

jvm原理(29)構造方法靜態程式碼塊位元組指令

上一節解析完了常量池,接下來是訪問標記, 00 21 : ACC_SUPPER + ACC_PUBLIC 緊接著是類的名字,2個位元組:00 05 是5號常量 【#5 = Class #49 // co

css-浮動清除浮動的原理(清除浮動的原理你知道嗎)

alt col ges mage all strong splay height http float元素A的特點: 脫離文檔流 靠向left或right float元素會和塊盒子重疊 準確來說,是塊盒子和A重疊,但塊盒子內容會浮動在A周圍 不會和inline元素重

NFS服務器原理和安裝配置案例演練

隨機選擇 span 通訊 操作系統 不同 網絡 定義 重新啟動 exportfs NFS服務器原理和安裝配置詳解附案例演練 1、什麽是NFS服務器   NFS就是Network File System的縮寫,它最大的功能就是可以通過網絡,讓不同的機器、不同的操作系統可以共享

AJAX 狀態值(readyState)狀態(status)

正在 初始 exp char cep 區別 expect 方式 分享 AJAX 狀態值(readyState)與狀態碼(status)詳解 1- AJAX狀態值與狀態碼區別AJAX狀態值是指,運行AJAX所經歷過的幾種狀態,無論訪問是否成功都將響應的步驟

Redis Sentinel實現的機制原理

過程 正則 發送 進行 還需 生產環境 根據 stat 時間 原文:Redis Sentinel實現的機制與原理詳解序言 Redis-Sentinel是Redis官方推薦的高可用性(HA)解決方案。實際上這意味著你可以使用Sentinel模式創建一個可以不用人為幹預而應對

php文件上傳原理(含源

輸入數據 最大值 cut 產生 har 默認值 接受 上傳文件 tle 1、文件上傳原理   將客戶端的文件上傳到服務器,再將服務器的臨時文件上傳到指定目錄 2、客戶端配置 提交表單 表單的發送方式為post 添加enctype="multipart/form-data"

華為交換機私有hybird接口模式:(案例+原理

華為 hybird 華為交換機私有hybird接口模式:(案例+原理詳解) 實驗說明: 準備:如圖pc1 pc2同屬於VLAN10,配置相應的ippc3 pc4同屬於VLAN20 配置相應的ipClient 屬於 VLAN30 配置pc1同網段ipPc1 pc2 client 屬於同網段

STP原理及SMTP案例分析

stp smtp STP工作原理:MSTP案例分析 一、 STP概述:邏輯上斷開環路,防止廣播風暴的產生 ,當線路故障阻塞接口被激活,恢復通信,起到備份線路的作用。二、STP工作原理 確定交換機角色: 根交換機———在一個VLAN中,有且只有一個根交換機。2.非根交換機——除了跟交換機意外,其

RIP概述、原理及實驗驗證

RIP 路由 RIP路由協議 一.路由類型: 直連路由——設備直連的網絡默認路由靜態路由——管理員手動配置的路由動態路由(IGP)internal gateway protocol1.DV—距離矢量路由協議——RIP IGRP EIGRP2.LS—鏈路狀態路由協議——ISIS OSPF 二.RI