spring+html5實現(xiàn)安全傳輸隨機數(shù)字密碼鍵盤
隨著互聯(lián)網(wǎng)的飛躍式發(fā)展,移動支付已越來越受歡迎并且已成為常態(tài),很多三方支付公司推出了很多支付方式如快捷支付、認(rèn)證支付、掃碼支付等等。快捷支付和認(rèn)證支付可分為移動app控件和移動HTML5網(wǎng)頁。用戶第一次使用快捷支付或認(rèn)證支付進行支付的時候,需先綁定銀行卡。在綁定銀行卡的過程中,需要驗證銀行卡信息。不同銀行、不同銀行卡驗證的要素不一樣,有些需要驗證四要素,有的需要驗證八要素。對于需要驗證銀行卡的交易密碼的情況,怎樣保證交易密碼的安全不被別人所竊取呢?為了保證交易密碼不在傳輸過程中被竊取,出現(xiàn)了安全傳輸隨機數(shù)字密碼鍵盤。
安全傳輸隨機數(shù)字密碼鍵盤怎么實現(xiàn)呢?今天給大家詳細(xì)的介紹安全傳輸隨機數(shù)字密碼鍵盤的原理和代碼實現(xiàn)。下圖是實現(xiàn)的數(shù)
字鍵盤效果:

一、實現(xiàn)原理
用戶點擊“交易密碼”輸入框,頁面異步向后臺發(fā)送“獲取密碼鍵盤”的請求,后臺接收到請求之后隨機生成“1234567890與隨機密文的對應(yīng)關(guān)系”和“隨機密文”和“1234567890圖片“的對應(yīng)關(guān)系,然后把它們關(guān)系放入dto實例中并放入redis中,最后把隨機密文以集合的方式返回到頁面,頁面js獲取到密文集合后以循環(huán)的方式向后臺請求對應(yīng)的數(shù)字圖片流,并展示在頁面。
當(dāng)用戶點擊數(shù)字鍵盤中的數(shù)字圖片,就會把圖片對應(yīng)的密文放入到pkey隱藏輸入框中,多個數(shù)字以逗號隔開,當(dāng)點擊支付的時候,就會把peykey隱藏輸入框的值傳入到后臺,后臺從redis中取出“密文”與“1234567890數(shù)字“的對應(yīng)關(guān)系,就取出了對應(yīng)交易密碼。
二、具體實現(xiàn)
1).HTML5頁面
頁面主要展示密碼輸入框和支付按鈕,需要導(dǎo)入JQuery、bootstrap及pwdkey.js等。下面是具體代碼:
<%@ page language="java" import="java.util.*"
contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
%>
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html>
<head>
<meta name="viewport"
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<meta http-equiv="Cache-Control" CONTENT="private,must-revalidate">
<link rel="stylesheet"
href='<c:url value="/js/bootstrap/css/bootstrap.min.css"/>'>
<!-- 引入js腳本文件 begin -->
<!--[if lt IE 9]>
<script src="http://cdn.bootcss.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="http://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="<c:url value="/js/JQuery/jquery-1.10.0.min.js"/>"></script>
<script src="<c:url value="/js/bootstrap/js/bootstrap.min.js"/>"></script>
<script type="text/javascript" src="<c:url value="/js/pwdkey.js"/>"></script>
<title>xxx付款</title>
<style type="text/css">
.input-out {
padding-top: 20px;
}
.btn-out {
margin:30px 10px;
}
.btn-out button {
width: 100%;
background: #5CACEE;
border: #5CACEE;
color: #fff;
height: 50px;
border-radius: 3px;
font-size: 18px;
font-family: "Microsoft Yahei", "??????", SimHei, Tahoma, Arial, Helvetica, STHeiti;
}
.keyboard {
background: #fff;
}
.keyboard table {
width:100%;
text-align:center;
}
.keyboard table td {
padding: 15px;
}
.keyboard table a,
.keyboard table a:hover,
.keyboard table a:focus {
color: #333;
text-decoration: none;
}
.input-out label {
color:#D2D1D1;
font-weight: normal;
font-size: 16px;
}
.bottom {
color:#888888;
margin-bottom: 15px;
text-align:center;
margin-top:100px;
}
.bottom a {
color:#888888;
}
.bottom img {
vertical-align: middle;
width: 18px;
}
</style>
</head>
<body>
<form action="<%=path%>/pay" method="post" id="from">
<div class="content">
<div class="input-out pass-label" style="border-bottom: 1px solid #ddd;padding-left: 12px;" random="2321321321" path="<%=path%>" >
<label id="pin" >交易密碼</label>
</div>
</div>
<div class="btn-out">
<button type="button" class="btn btn-default" ontouchstart="check();" id="pay">支付</button>
</div>
</form>
<div class="bottom" id="bottom-out">
<img src="<c:url value="/images/phone.png"/>" />
<span>客服電話:4000-xxx-xxx</span>
</div>
<!-- jianpan-->
<div class="keyboard" style="display:none;" id="keyboard">
<table class="table-bordered" id="key_table">
</table>
</div>
</body>
</html>
2).密碼鍵盤js代碼
用戶點擊“交易密碼”輸入框,頁面異步向后臺發(fā)送“獲取密碼鍵盤”的請求,后臺接收到請求之后把隨機密文以集合的方式返回到頁面,頁面js獲取到密文集合后以循環(huán)的方式向后臺請求對應(yīng)的數(shù)字圖片流并展示在頁面。具體代碼如下:
$(document).ready(
function() {
$("#pay").removeAttr("disabled");
$("input").click(function() {
hideKey();
});
$("button").click(function() {
hideKey();
});
$(".pass-label").click(function() {
var rangdom = $(this).attr("random");
var path = $(this).attr("path");
pwdkey(this, rangdom, path);
});
window.addEventListener(
"onorientationchange" in window ? "orientationchange"
: "resize", hengshuping, false);
});
function hengshuping() {
if (window.orientation == 180 || window.orientation == 0) {
$("div#keyboard td").each(function() {
$(this).css("padding", "15px");
});
}
if (window.orientation == 90 || window.orientation == -90) {
$("div#keyboard td").each(function() {
$(this).css("padding", "8px");
});
}
window.scrollTo(0, $(".pass-label").offset().top);
}
function pwdkey(obj, rangdom, path) {
$('.keyboard').addClass("navbar-fixed-bottom");
$('.keyboard').css({
"z-index" : "9999"
});
if (rangdom == null || rangdom == "") {
alert("無法加載密碼鍵盤,請刷新后重試!");
return false;
}
if ($("#pkey").val() == null || $("#pkey").val() == "undefined") {
$(obj)
.html(
$(obj).html()
+ '<input type="hidden" name="pkey" id="pkey" />');
}
$("#pin").html("交易密碼");
setCssNomal();
$("#pkey").val("");
$
.ajax({
type : 'post',
url : path + "/common/pkey.do",
cache : false,
async : false,
data : {
rangdom : rangdom
},
success : function(data) {
if (data == null || data == "" || data == "undefined"
|| data.length != 10) {
alert("無法加載密碼鍵盤,請刷新后重試!");
return false;
} else {
var key_table = $("#key_table");
key_table.html("");
var content = '<tr>';
for (var i = 0; i < 12; i++) {
if ((i + 1) % 3 == 0 && i != 0 && i <= 5) {
content = content
+ '<td style="width:33%;" key="'
+ data[i]
+ '" ontouchstart="return ontouch(this);"><img src="'
+ path
+ '/common/getKey.do?key='
+ data[i]
+ '&rangdom='
+ rangdom
+ '" style="height:20px;" id="key_img"/></td></tr><tr>';
} else if (i <= 7) {
content = content
+ '<td style="width:33%;" key="'
+ data[i]
+ '" ontouchstart="return ontouch(this);"><img src="'
+ path
+ '/common/getKey.do?key='
+ data[i]
+ '&rangdom='
+ rangdom
+ '" style="height:20px;" id="key_img"/></td>';
} else if (i == 8) {
content = content
+ '<td style="width:33%;" ontouchstart="return deleteOne();"><img src="'
+ path
+ '/images/keys/delete.png" style="height:20px;" id="key_img"/></td>'
+ '</tr><tr>';
} else if (i < 11) {
content = content
+ '<td style="width:33%;" key="'
+ data[i - 1]
+ '" ontouchstart="return ontouch(this);"><img src="'
+ path
+ '/common/getKey.do?key='
+ data[i - 1]
+ '&rangdom='
+ rangdom
+ '" style="height:20px;" id="key_img"/></td>';
} else {
content = content
+ '<td style="width:33%;" onclick="return _ok();"><img src="'
+ path
+ '/images/keys/ok.png" style="height:20px;" id="key_img"/></td>'
+ '</tr>';
}
}
key_table.html(content);
setTimeout(function() {
$("#keyboard").show();
}, 600);
hengshuping();
}
},
error : function() {
alert("無法加載鍵盤,請刷新后重試!");
}
});
}
function ontouch(obj) {
var pkey = $("#pkey").val();
var key = $(obj).attr("key");
if (pkey == "") {
$("#pin").html("");
}
var content = $("#pin").html();
if (content != "" && content.length >= 6) {
return false;
}
if (pkey != "") {
key = "," + key;
}
pkey = pkey + key;
$("#pkey").val(pkey);
$("#pin").append("*");
setCssKey();
}
function deleteOne() {
var pkey = $("#pkey").val() + "";
if (pkey == "") {
return false;
}
var local = pkey.lastIndexOf(",");
if (local == -1) {
$("#pkey").val("");
$("#pin").html("交易密碼");
setCssNomal();
} else {
pkey = pkey.substring(0, local - 1);
var content = $("#pin").html();
content = content.substring(0, content.length - 1);
$("#pkey").val(pkey);
$("#pin").html(content);
}
}
function _ok() {
$("#key_table").html("");
$("#keyboard").hide();
}
function showkey() {
$("#keyboard").show();
}
function hideKey() {
$("#key_table").html("");
$("#keyboard").hide();
}
function setCssKey() {
$("#pin").css({
"font-size" : "18px",
"color" : "#030303",
"font-weight" : "normal",
"letter-spacing" : "1px"
});
}
function setCssNomal() {
$("#pin").css({
"font-size" : "16px",
"color" : "#D2D1D1",
"font-weight" : "normal"
});
}
3).獲取密碼鍵盤后臺方法
該方法將隨機生成“1234567890與隨機密文的對應(yīng)關(guān)系”和“隨機密文”和“1234567890圖片“的對應(yīng)關(guān)系,然后把它們關(guān)系放入dto實例中并放入redis中,最后把隨機密文以集合的方式返回到頁面。具體代碼如下:
獲取密碼鍵盤:
/**
*
* @Description: 獲取密碼鍵盤
* @param request
* @param rangdom 隨機字符串
* @return
*
*/
@SuppressWarnings("unchecked")
@ResponseBody
@RequestMapping(value = "common/pkey.do", method = RequestMethod.POST)
public Object digitkeyboard(HttpServletRequest request, String rangdom) {
LOG.info("[獲取密碼鍵盤digitkeyboard][parames:outOrderId=" + rangdom + "]");
try {
if (StringUtils.isBlank(rangdom)) {
return "";
}
PwdKeyDto pwdkey = PwdKeyUtils.digitkeyboard();
redisUtil.set("pwdkey_" + rangdom, pwdkey,
redisUtil.getDigitkeyimeOut());
return pwdkey.getRundomKeys();
} catch (Exception e) {
LOG.error("[獲取密碼鍵盤digitkeyboard][error:{}",e);
}
return "";
}
密碼PwdKeyDto:
/**
*
* @ClassName: PwdKeyDto
* @Description: 密碼映射Dto
* @author xxxx <a target="_blank" href="mailto:xxxx@xxx.com" rel="external nofollow" >xxxx@xxx.com</a>
* @date 2015年6月25日 上午11:01:20
*
*/
public class PwdKeyDto implements Serializable{
/**
描述*/
private static final long serialVersionUID = 1L;
private List<String> rundomKeys;// 隨機Keys
private Map<String, String> valueKeyMaps;// 密文和明文映射
private Map<String, String> imgKeyMaps;// 密文和明文映射
public PwdKeyDto() {
super();
// TODO Auto-generated constructor stub
}
public List<String> getRundomKeys() {
return rundomKeys;
}
public void setRundomKeys(List<String> rundomKeys) {
this.rundomKeys = rundomKeys;
}
public PwdKeyDto(List<String> rundomKeys, Map<String, String> valueKeyMaps,
Map<String, String> imgKeyMaps) {
super();
this.rundomKeys = rundomKeys;
this.valueKeyMaps = valueKeyMaps;
this.imgKeyMaps = imgKeyMaps;
}
public Map<String, String> getValueKeyMaps() {
return valueKeyMaps;
}
public void setValueKeyMaps(Map<String, String> valueKeyMaps) {
this.valueKeyMaps = valueKeyMaps;
}
public Map<String, String> getImgKeyMaps() {
return imgKeyMaps;
}
public void setImgKeyMaps(Map<String, String> imgKeyMaps) {
this.imgKeyMaps = imgKeyMaps;
}
}
生成鍵盤的PwdKeyUtils工具類:
/**
*
* @ClassName: PwdKeyUtils
* @Description: 密碼處理工具類
* @author xxxx <a target="_blank" href="mailto:xxxx@xxxx.com" rel="external nofollow" >xxxx@xxxx.com</a>
* @date 2015年6月25日 上午11:03:24
*
*/
public class PwdKeyUtils {
private final static Map<String, String> imagesValueMap;
/**
*
* @Description: 獲取密碼鍵盤映射關(guān)系
* @param imagesValueMap
* @return
*
*/
static {
imagesValueMap = new HashMap<String, String>();
imagesValueMap.put("0",
"images/keys/0.png");
imagesValueMap.put("1",
"images/keys/1.png");
imagesValueMap.put("2",
"images/keys/2.png");
imagesValueMap.put("3",
"images/keys/3.png");
imagesValueMap.put("4",
"images/keys/4.png");
imagesValueMap.put("5",
"images/keys/5.png");
imagesValueMap.put("6",
"images/keys/6.png");
imagesValueMap.put("7",
"images/keys/7.png");
imagesValueMap.put("8",
"images/keys/8.png");
imagesValueMap.put("9",
"images/keys/9.png");
}
public static PwdKeyDto digitkeyboard() {
List<String> rundomKeys = new ArrayList<String>();// 隨機key映射
Map<String, String> valueKeys = new HashMap<String, String>();// 密文和明文映射
Map<String, String> imgKeyMaps = new HashMap<String, String>();// 密文和圖片映射
List<String> keys = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
keys.add(i + "");
}
for (int i = 0; i < 10; i++) {
Random r = new Random();
int index = r.nextInt(keys.size());
String key = keys.get(index);
keys.remove(index);
String randomkey = randomKey(24);
rundomKeys.add(randomkey);
valueKeys.put(randomkey, key);
imgKeyMaps.put(randomkey, imagesValueMap.get(key));
}
PwdKeyDto dto = new PwdKeyDto(rundomKeys, valueKeys, imgKeyMaps);
return dto;
}
/**
*
* @Description:獲取動態(tài)key
* @param num
* key位數(shù)
* @return
*
*/
public static String randomKey(int num) {
StringBuffer sb = new StringBuffer("");
char[] chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', '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', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z' };
for (int i = 0; i < num; i++) {
int id = (int) Math.ceil(Math.random() * 60);
sb.append(chars[id]);
}
return sb.toString();
}
/**
*
* @Description:解密pin
* @param request
* @param pin
* @return
*
*/
public static String decryptPinData(HttpServletRequest request,
String ciphertextpin) throws Exception {
if (StringUtils.isNotBlank(ciphertextpin)) {
Map<String, String> valuekeys = (Map<String, String>) request
.getSession().getAttribute("valuekeys");
if (valuekeys == null || valuekeys.size() != 10) {
throw new Exception();
}
String[] ciphertextpins = ciphertextpin.split(",");
StringBuffer sb = new StringBuffer("");
for (String ctpin : ciphertextpins) {
sb.append(valuekeys.get(ctpin));
}
}
return null;
}
}
4).獲取圖片流后臺方法
用戶頁面獲取到隨機密文集合后以循環(huán)的方式向后臺請求該方法獲取對應(yīng)的數(shù)字圖片流。具體代碼如下:
/**
* 獲取key圖片
*
* @throws Exception
*/
@RequestMapping(value = "/common/getKey.do", method = RequestMethod.GET)
public void getKey(String key, String rangdom, HttpServletRequest request,
HttpServletResponse response) throws Exception {
LOG.info("[獲取密碼鍵盤key(getKey)][parms:key=" + key + "]");
PwdKeyDto pwdkey = (PwdKeyDto) redisUtil.get("pwdkey_" + rangdom);
if (pwdkey == null || pwdkey.getImgKeyMaps() == null) {
LOG.error("獲取圖片[getKey]:未獲取到對應(yīng)的鍵盤的映射關(guān)系");
throw new Exception();
}
Map<String, String> imagekeys = pwdkey.getImgKeyMaps();
String path = imagekeys.get(key);
String rootPath = request.getSession().getServletContext()
.getRealPath("/");
path = rootPath + path;
LOG.info("[獲取密碼鍵盤key(getKey)][path=" + path + "]");
if (StringUtils.isNotEmpty(path)) {
try {
InputStream fis = new FileInputStream(new File(path));
BufferedInputStream bis = new BufferedInputStream(fis);
OutputStream fos = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(fos);
String fileName = "image.";
String[] strs = path.split("\\.");
fileName = fileName + strs[strs.length - 1];
setFileDownloadHeader(request, response, fileName);
int byteRead = 0;
byte[] buffer = new byte[8192];
while ((byteRead = bis.read(buffer, 0, 8192)) != -1) {
bos.write(buffer, 0, byteRead);
}
bos.flush();
fis.close();
bis.close();
fos.close();
bos.close();
} catch (Exception e) {
LOG.error("獲取圖片[path:" + path + "])失?。? + e.toString(), e);
}
}
}
5).用戶支付
當(dāng)用戶點擊數(shù)字鍵盤中的數(shù)字圖片,就會把圖片對應(yīng)的密文放入到pkey隱藏輸入框中,多個數(shù)字以逗號隔開,當(dāng)點擊支付的時候,就會把peykey隱藏輸入框的值傳入到后臺,后臺從redis中取出“密文”與“1234567890數(shù)字“的對應(yīng)關(guān)系,就取出了對應(yīng)交易密碼。具體代碼如下:
頁面提交支付js:
function check()
{
hideKey();
var pin="";
pin=$("#pkey").val();
if(pin==""||pin==undefined)
{
bool=false;
alert("請輸入交易密碼");
return false;
}else
{
var keys=pin.split(",");
if(keys.length!=6)
{
alert("請輸入6位交易密碼");
return false;
}
}
$.ajax({
type : 'post',
url : "test/pay.do",
data : {
random:"2321321321",
pin:pin
},
cache : false,
success : function(data) {
if(data.success)
{
alert(data.message);
}else{
}
},
error : function(){
alert("系統(tǒng)異常,請重試!");
}
});
}
后臺解析密文方法:
/**
*
* @Description: 支付
* @param pin 交易密碼密文
* @param random 隨機碼
* @return
*
*/
@ResponseBody
@RequestMapping(value = "/test/pay.do", method = RequestMethod.POST)
public Object pay(String pin,String random, HttpServletRequest request) {
try {
LOG.info("[支付(pay)][params:pin=" + pin + ",random="+random+"]");
if (StringUtils.isNotBlank(pin)) {
StringBuffer sb = new StringBuffer("");
PwdKeyDto pwdkey = (PwdKeyDto) redisUtil.get("pwdkey_" + random);
if (pwdkey == null || pwdkey.getValueKeyMaps() == null) {
return new Result(false,"密碼鍵盤已失效,請重新輸入密碼");
}
Map<String, String> valuekeys = pwdkey.getValueKeyMaps();
String[] pins = pin.split(",");
if (pins.length != 6) {
return new Result(false,"交易密碼位數(shù)不對");
}
for (String pinKey : pins) {
String val = valuekeys.get(pinKey);
if (StringUtils.isBlank(val)) {
return new Result(false,"密碼鍵盤已失效,請重新輸入密碼");
}
sb.append(val);
}
/**
* sb.toString()就是明文交易密碼,下面就是具體的支付操作
*/
}
return new Result(true, "成功");
} catch (Exception e) {
LOG.error("[支付(pay)][error:{}]",e);
return new Result(false, "支付異常,請重試!");
}
}
以上就是對使用spring+html5實現(xiàn)安全傳輸隨機數(shù)字密碼鍵盤的介紹和代碼實現(xiàn),大家有什么疑問或設(shè)計的不合理的地方可以一起討論。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot2.0 整合 SpringSecurity 框架實現(xiàn)用戶權(quán)限安全管理方法
- 詳解spring security安全防護
- Spring Boot使用過濾器和攔截器分別實現(xiàn)REST接口簡易安全認(rèn)證示例代碼詳解
- spring mvc中直接注入的HttpServletRequst安全嗎
- Spring中如何獲取request的方法匯總及其線程安全性分析
- spring cloud-給Eureka Server加上安全的用戶認(rèn)證詳解
- Spring Boot 實例代碼之通過接口安全退出
- Spring Boot如何使用Spring Security進行安全控制
- Spring Security+Spring Data Jpa如何進行安全管理
相關(guān)文章
修改idea的這些啟動參數(shù),令你的idea健步如飛
這篇文章主要介紹了修改idea的這些啟動參數(shù),令你的idea健步如飛~具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
JAVA線程池監(jiān)控以及動態(tài)調(diào)整示例詳解
這篇文章主要為大家介紹了JAVA線程池監(jiān)控以及動態(tài)調(diào)整示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09

