放暑假之前,在网上闲逛,不知怎么的就逛到了CoolCode.CN,看到了andot老师的文章《安全登录系统的设计与实现方案》,深受启发,决定将其进行实际应用。但是文章中仅仅指出了大致的流程,并没有描述具体操作方法。经过一下午+一晚上的摸索,终于将其实现。本文就将介绍我在实践过程中的一些感想和经验。
平时我们在非SSL加密连接的网站上递交登录表单都是使用明文提交密码,服务器收到客户端递交的密码之后,用相应的加密算法加密(比如MD5或者是SHA1,本文以MD5为例),与数据库中MD5过的密码进行比对,判断用户是否合法。但是在这种情况下,一旦用户发送的明文密码被截获,用户的安全就受到了严重威胁。
依据andot老师的文章,使用Javascript+PHP实现安全登录的最简便安全的方法就是服务器提供随机字串并将其存储,Javascript在客户端使用MD5将随机数和密码合并进行运算,将MD5过的字符串提交给服务器,服务器方也作相同的运算,与客户端提交的结果进行比对,就可以判断用户的身份了。这样,就算截获中途传输的数据,也只是一个MD5过的密码和一个一次性的随机字符串再次MD5运算的产物而已,随机字符串一旦使用过就会被立即销毁,无法再次验证,没有丝毫用处。而就算中途截获随机字符串,若不知道密码原文,依然无法向服务器递交合法的验证字符串。密码原文只在客户端浏览器上出现,不通过网络传输,每次提交的验证用的密码都不同,连服务器都不知道原文是什么,从而达到保护密码原文的目的。
大致原理清楚之后,下面就是具体工作流程。
首先,用户在客户端填写表单,点击提交按钮,触发Javascript。Javascript异步向服务器端请求随机字符串,服务器端生成字符串,储存于SESSION,同时作为XML发送回客户端。客户端的Javascript收到XML并进行解析,得到随机字符串。
随后,Javascript取得用户输入的密码原文,将其进行MD5。之后将MD5之后的密码与服务器发来的随机字符串再次进行MD5。
之后,Javascript将加密过后的密码与用户名提交给服务器,服务器用SESSION中的随机字符串与数据库中的密码进行MD5并销毁SESSION中的随机字符串,将结果与用户提交的结果进行比对,判断用户身份。如果通过验证,服务器将用户的状态、帐号等信息存储与SESSION。
最后,服务器将登陆状态再次通过XML返回给客户端,如果返回成功,则客户端显示登陆成功,并将浏览器重新定向至相关页面。至此登陆过程结束。
事实上,这个过程非常简单,实现起来只需要5个Javascript函数,一个用来创建XMLHttpRequest对象,一个用来向服务器申请随机字符串,一个用来取得随即字符串,一个用来向服务器发送用户提交的用户名和密码,一个用来取得登陆结果并判断是否需要重定向。如果还需要一个进度条或者是当前登录状态的显示,至需要再增加一到两个控制页面显示的函数就可以了。
下面是AJAX代码:
/*
* SAFE LOGIN
* Version 0.2 Copyright (C) Dgwxx 2006.
* See http://www.dgwxx.net for more info.
*/
var xmlHttp;
setStatus("准备...", "green");
function createXMLHttpRequest() {
if (window.ActiveXObject) {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
}
}
function requestRndCode() {
setStatus("正在请求验证码...", "green");
createXMLHttpRequest();
var url = "user.php?action=rnds&timeStamp=" + new Date().getTime();
xmlHttp.open("GET", url, true);
xmlHttp.onreadystatechange = processRndcode;
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send(null);
}
function processRndcode(){
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
setStatus("正在处理验证码...", "green");
var rndcode = xmlHttp.responseXML.getElementsByTagName("rndcode")[0].firstChild.data;
sendVerifyRequest(rndcode);
}
}
function sendVerifyRequest(rndstr) {
setStatus("发送验证请求...", "green");
createXMLHttpRequest();
var url = "user.php?action=verify&timeStamp=" + new Date().getTime();
var loginuser = document.getElementById("loginuser").value;
var loginpwd = document.getElementById("loginpwd").value;
var sendpwd = hex_md5(rndstr + hex_md5(loginpwd));
xmlHttp.open("POST", url, true);
xmlHttp.onreadystatechange = processVerifyRequest;
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send("loginuser=" + loginuser + "&loginpwd=" + sendpwd);
setStatus("正在等待服务器回应...", "green");
return false;
}
function processVerifyRequest() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
setStatus("正在处理验证...", "green");
var val = xmlHttp.responseXML.getElementsByTagName("passed")[0].firstChild.data;
if (val == "true") {
setStatus("验证成功...", "green");
setTimeout("window.location.href='config.php'",500);
} else {
setStatus("验证失败...", "red");
}
}
}
function setStatus(message, fontColor) {
document.getElementById("status").innerHTML = "<font color=" + fontColor + ">安全验证状态 : " + message + " </font>";
}
PHP分配随机字符串的代码:
<?
rndcode=md5( nowtime_stamp . sitename); _SESSION['rnd_user_key'] = rndcode;header("Content−type:application/xml");echo′<?xmlversion="1.0"encoding="UTF−8"?><response><rndcode>′. rnd_code.'</rndcode>
</response>';
?>
平时我们在非SSL加密连接的网站上递交登录表单都是使用明文提交密码,服务器收到客户端递交的密码之后,用相应的加密算法加密(比如MD5或者是SHA1,本文以MD5为例),与数据库中MD5过的密码进行比对,判断用户是否合法。但是在这种情况下,一旦用户发送的明文密码被截获,用户的安全就受到了严重威胁。
依据andot老师的文章,使用Javascript+PHP实现安全登录的最简便安全的方法就是服务器提供随机字串并将其存储,Javascript在客户端使用MD5将随机数和密码合并进行运算,将MD5过的字符串提交给服务器,服务器方也作相同的运算,与客户端提交的结果进行比对,就可以判断用户的身份了。这样,就算截获中途传输的数据,也只是一个MD5过的密码和一个一次性的随机字符串再次MD5运算的产物而已,随机字符串一旦使用过就会被立即销毁,无法再次验证,没有丝毫用处。而就算中途截获随机字符串,若不知道密码原文,依然无法向服务器递交合法的验证字符串。密码原文只在客户端浏览器上出现,不通过网络传输,每次提交的验证用的密码都不同,连服务器都不知道原文是什么,从而达到保护密码原文的目的。
大致原理清楚之后,下面就是具体工作流程。
首先,用户在客户端填写表单,点击提交按钮,触发Javascript。Javascript异步向服务器端请求随机字符串,服务器端生成字符串,储存于SESSION,同时作为XML发送回客户端。客户端的Javascript收到XML并进行解析,得到随机字符串。
随后,Javascript取得用户输入的密码原文,将其进行MD5。之后将MD5之后的密码与服务器发来的随机字符串再次进行MD5。
之后,Javascript将加密过后的密码与用户名提交给服务器,服务器用SESSION中的随机字符串与数据库中的密码进行MD5并销毁SESSION中的随机字符串,将结果与用户提交的结果进行比对,判断用户身份。如果通过验证,服务器将用户的状态、帐号等信息存储与SESSION。
最后,服务器将登陆状态再次通过XML返回给客户端,如果返回成功,则客户端显示登陆成功,并将浏览器重新定向至相关页面。至此登陆过程结束。
事实上,这个过程非常简单,实现起来只需要5个Javascript函数,一个用来创建XMLHttpRequest对象,一个用来向服务器申请随机字符串,一个用来取得随即字符串,一个用来向服务器发送用户提交的用户名和密码,一个用来取得登陆结果并判断是否需要重定向。如果还需要一个进度条或者是当前登录状态的显示,至需要再增加一到两个控制页面显示的函数就可以了。
下面是AJAX代码:
/*
* SAFE LOGIN
* Version 0.2 Copyright (C) Dgwxx 2006.
* See http://www.dgwxx.net for more info.
*/
var xmlHttp;
setStatus("准备...", "green");
function createXMLHttpRequest() {
if (window.ActiveXObject) {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
}
}
function requestRndCode() {
setStatus("正在请求验证码...", "green");
createXMLHttpRequest();
var url = "user.php?action=rnds&timeStamp=" + new Date().getTime();
xmlHttp.open("GET", url, true);
xmlHttp.onreadystatechange = processRndcode;
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send(null);
}
function processRndcode(){
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
setStatus("正在处理验证码...", "green");
var rndcode = xmlHttp.responseXML.getElementsByTagName("rndcode")[0].firstChild.data;
sendVerifyRequest(rndcode);
}
}
function sendVerifyRequest(rndstr) {
setStatus("发送验证请求...", "green");
createXMLHttpRequest();
var url = "user.php?action=verify&timeStamp=" + new Date().getTime();
var loginuser = document.getElementById("loginuser").value;
var loginpwd = document.getElementById("loginpwd").value;
var sendpwd = hex_md5(rndstr + hex_md5(loginpwd));
xmlHttp.open("POST", url, true);
xmlHttp.onreadystatechange = processVerifyRequest;
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send("loginuser=" + loginuser + "&loginpwd=" + sendpwd);
setStatus("正在等待服务器回应...", "green");
return false;
}
function processVerifyRequest() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
setStatus("正在处理验证...", "green");
var val = xmlHttp.responseXML.getElementsByTagName("passed")[0].firstChild.data;
if (val == "true") {
setStatus("验证成功...", "green");
setTimeout("window.location.href='config.php'",500);
} else {
setStatus("验证失败...", "red");
}
}
}
function setStatus(message, fontColor) {
document.getElementById("status").innerHTML = "<font color=" + fontColor + ">安全验证状态 : " + message + " </font>";
}
PHP分配随机字符串的代码:
<?
rndcode=md5( nowtime_stamp . sitename); _SESSION['rnd_user_key'] = rndcode;header("Content−type:application/xml");echo′<?xmlversion="1.0"encoding="UTF−8"?><response><rndcode>′. rnd_code.'</rndcode>
</response>';
?>