验证码后台验证
登陆页面的验证码一直是大家头痛的问题,目前多数的验证码走的都是前端验证,但前端验证有许多危险漏洞,本文是一个简单的后台验证码实现,详细请看如下所述。
实现方式:我的框架是struts+spring+jdbc 为了越过struts 配置文件 选择使用serverlet 方式。
先附图看下验证码样式
第一步前端页面
jsp页面代码如下,其中引用了两个js文件(js中触发from表单提交)
<script type="text/javascript" src="js/login.js"></script>
<!-- 验证码图片 要自己写一个输入的文本框 -->
<img src="/ZQZC-WEB/codeimage" id="Verify" style="position: relative;top:333px;left:307px;width:68px;height:36px;" alt="看不清,换一张"/>
<script src="js/JSCode.js"></script>
<script>
<form action="sys/loginAction.do" method="post" id="loginForm">
//<input type="hidden" name="isIOS" id="isIOS" value=""/>
//<input type="hidden" name="password" id="password" value=""/>
//<input type="hidden" name="loginName" id="loginName" value=""/>
<input type="hidden" name="codeCon" id="codeCon_" value=""/>
</form>
$(function(){
//点击图片更换验证码
$("#Verify").click(function(){
/* 点击图片发送请求,设置了struts拦截器允许该请求通过 */
$(this).attr("src","/ZQZC-WEB/codeimage?timestamp="+new Date().getTime());
});
});
</script>
login.js 其中js中有些代码没用到可根据个人使用删除
/**
* 登陆调用的方法
*/
function checklogin(){
if (!$("#inputloginName").val()){
$("#message").html("请输入用户名!");
$("#inputloginName").focus();
return false;
}
var loginNameReg = /^[a-zA-Z0-9_]{3,32}$/i;
if (!loginNameReg.test($("#inputloginName").val())){
$("#message").html("用户名非法,请重新输入!");
$("#inputloginName").focus();
return false;
}
if (!$("#inputpassword").val()){
$("#message").html("请输入密码!");
$("#inputpassword").focus();
return false;
}
if (!$("#codeCon").val()){
$("#message").html("请输入验证码!");
$("#codeCon").focus();
return false;
}
$("#password").val($.md5($("#inputpassword").val()));
$("#loginName").val($("#inputloginName").val());
//新增验证码
$("#codeCon_").val($("#codeCon").val());
$("#loginForm").submit();
}
/**
* 为了在火狐和谷歌下支持回车可以调用登陆
*/
function keypress(e)
{
var currKey=0,e=e||event;
if(e.keyCode==13){
//回车事件会自动触发对象的click事件,如果在这里直接调用checklogin会导致该方法执行两次,回车调用一次,click调用一次
//结果会有两条登入日志,解决办法就是在回车事件中触发click
$("input[type='image']").click();
return false;
}else return true;
}
document.οnkeypress=keypress;
/**
* 获得焦点
*/
function getFocus(str){
$("#"+str).focus();
}
/**
* 增加常见问题页面的链接
*/
function problem(){//ZQL 20110707
javascript:win=open('njsp/ExtJsp/problem/problem.jsp', 'content', 'width=802,height=450,left=230,top=130,location=no,toolbar=no,status=no,resizable=no,scrollbars=no,menubar=no,directories=no');
}
/**
* 用来得到字符串的长度
*/
function getlength(str){
//str表示确认密码或者新密码
var strvalue = document.getElementById(str).value;//用户输入的新密码或者确认密码的值
var realLength = 0, len = strvalue.length, charCode = -1;
for (var i = 0; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode >= 0 && charCode <= 128) realLength += 1;
else realLength += 2;
}
return realLength*1;
}
/**
* 用来检查密码必须包含大小写字母和数字
* */
function CheckStr(s){
return /[a-zA-Z0-9]*[a-z]+[a-zA-Z0-9]*/.test(s)&&/[a-zA-Z0-9]*[A-Z]+[a-zA-Z0-9]*/.test(s)&&/[a-zA-Z0-9]*[0-9]+[a-zA-Z0-9]*/.test(s);
}
/**
* 修改密码提交时用到的验证
*/
function checkform(forma){
var password = $("#password").val();//用户输入的旧密码
var newpassword = $("#newpassword").val();//用户输入的新密码
var verifypassword = $("#verifypassword").val();//用户输入的确认密码
var dbpassword = $("#dbpassword").val();//用户的数据库密码
if(!password){
$("#message").html("请输入旧密码!");
$("#password").focus();
return false;
}
if(($.md5(password))!=dbpassword){
$("#message").html("密码错误,请重新输入!");
$("#password").focus();
return false;
}
if(!newpassword){
$("#message").html("请输入新密码!");
$("#newpassword").focus();
return false;
}
if(getlength("newpassword")<8){
$("#message").html("密码长度至少为8!");
$("#newpassword").focus();
return false;
}
if(CheckStr(newpassword)==false){
$("#message").html("密码必须包含大小写字母和数字!");
$("#newpassword").focus();
return false;
}
if(!verifypassword){
$("#message").html("请输入确认密码!");
$("#verifypassword").focus();
return false;
}
if(newpassword!=verifypassword){
$("#message").html("两次输入的密码不一致!");
$("#verifypassword").focus();
return false;
}
// $("#password").val($.md5(password));
// $("#newpassword").val($.md5(newpassword));
// $("#verifypassword").val($.md5(newpassword));
var postData = new Object();
postData[$("#password").attr("name")]=$.md5(password);
postData[$("#newpassword").attr("name")]=$.md5(newpassword);
postData[$("#verifypassword").attr("name")]=$.md5(verifypassword);
$.ajax({
url:$("#updatePwdForm").attr("action"),
data:postData,
type:"POST",
success:function(data, textStatus, jqXHR){
if(data.success){
alert("密码修改成功,请重新登录!");
window.top.location.href=$("#updatePwdForm").attr("doneurl");
}else{
alert(data.msg);
}
}
});
// $("#updatePwdForm").submit();
// alert("密码修改成功,请重新登录!");
// window.parent.location.href='../login.jsp';
}
JSCode.js
//解决ie7/8下浏览器不兼容trim
String.prototype.trim = function () {
return this .replace(/^\s\s*/, '' ).replace(/\s\s*$/, '' );
}
//生成一定长度的随机数字和字母
function randomString(len) {
len = len || 32;
var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
var maxPos = $chars.length;
var pwd = '';
for (i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
function randomColor() {
var rand = Math.floor(Math.random( ) * 0xFFFFFF).toString(16);
if(rand.length == 6){
return rand;
}else{
return randomColor();
}
}
function createCode(n,obj){
var codeHtml="";
var codeVal="";
codeHtml+="<div id='codeShade' style='border:1px solid red;position:absolute;width:66px;height:30px;z-index:10;background:#"+randomColor()+";opacity:0.2;filter:alpha(opacity=20)'></div>"
for(var i=0;i<n;i++){
var codeColor='#'+randomColor();
var strCode = "<span style='color:"+codeColor+";display:inline-block;width:17px;height:27px;line-height:27px;font-size:30px;'>";
var OneCode = randomString(1);
strCode+=OneCode;
codeVal+=OneCode;
strCode+="</span>"
codeHtml+=strCode;
}
codeHtml+="<span id='codeVal' style='display:none;'>"+codeVal+"</span>";
obj.html(codeHtml);
$("#codeShade").click(function(){
createCode(n,obj);
});
}
function CheckYN(obj){
if(obj.val().trim().toUpperCase()==$("#codeVal").text().toUpperCase()){
return true;
}else{
return false;
}
}
第二步 后台
会用到三个类文件 SecurityCode(生成验证码),SecurityImage(绘制验证码图片),CodeImage(验证码和图片合并到一起并存入session以便调用)
SecurityCode.java
package cn.cntomorrow.beidian.model;
import java.util.Arrays;
/**
* 工具类,生成随机验证码字符串
* @version 1.0
* @author Lyan by 2017-08-03 20:00
*
*/
public class SecurityCode {
/**
* 验证码难度级别,Simple只包含数字,Medium包含数字和小写英文,Hard包含数字和大小写英文
*/
public enum SecurityCodeLevel {Simple,Medium,Hard};
/**
* 产生默认验证码,4位中等难度
* @return String 验证码
*/
public static String getSecurityCode(){
return getSecurityCode(4,SecurityCodeLevel.Medium,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=Arrays.copyOfRange(codes, 0,9);
}else if(level==SecurityCodeLevel.Medium){
codes=Arrays.copyOfRange(codes, 0,33);
}
//字符集合长度
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);
}
}
SecurityImage.java
package cn.cntomorrow.beidian.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;
import com.sun.image.codec.jpeg.ImageFormatException;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
* 验证码生成器类,可生成数字、大写、小写字母及三者混合类型的验证码。
* 支持自定义验证码字符数量;
* 支持自定义验证码图片的大小;
* 支持自定义需排除的特殊字符;
* 支持自定义干扰线的数量;
* 支持自定义验证码图文颜色
* @author Lyan by 2017-08-03 20:00
* @version 1.0
*/
public class SecurityImage {
/**
* 生成验证码图片
* @param securityCode 验证码字符
* @return BufferedImage 图片
*/
public static BufferedImage createImage(String securityCode){
//验证码长度
int codeLength=securityCode.length();
//字体大小
int fSize = 15;
int fWidth = fSize + 1;
//图片宽度
int width = 68;
//图片高度
int height = 36;
//图片
BufferedImage image=new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g=image.createGraphics();
//设置背景色
g.setColor(Color.WHITE);
//填充背景
g.fillRect(0, 0, width, height);
//设置边框颜色
g.setColor(Color.LIGHT_GRAY);
//边框字体样式
g.setFont(new Font("Arial", Font.BOLD, height - 2));
//绘制边框
g.drawRect(0, 0, 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 - 10;
//设置字体颜色和样式
g.setColor(new Color(19,148,246));
g.setFont(new Font("Georgia", Font.BOLD, fSize));
for(int i = 0; i < codeLength;i++){
g.drawString(String.valueOf(securityCode.charAt(i)), i * 16 + 5, codeY);
}
//关闭资源
g.dispose();
return image;
}
}
CodeImage.java
package cn.cntomorrow.beidian.action;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.cntomorrow.beidian.model.SecurityCode;
import cn.cntomorrow.beidian.model.SecurityImage;
/**
* 页面请求生成验证码
* by Lyan 2017-08-04 20:00
* */
public class CodeImage extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
//在doget中执行dopost方法
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
String securityCode = SecurityCode.getSecurityCode();
BufferedImage createImage = SecurityImage.createImage(securityCode);
// 将生成的验证码code放入sessoin中
req.getSession().setAttribute("code", securityCode);
// 通过ImageIO将图片输出
ImageIO.write(createImage, "JPG", resp.getOutputStream());
}
}
第三步 登陆的Action 去验证验证码 要生成codeCon的get,set 方法
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession gsession = request.getSession();
Object codeConq = gsession.getAttribute("code");
if (!codeCon.equals(codeConq.toString())) {
System.out.println(codeConq);
throw new Exception("验证码错误!!!");
}
第四步 web.xml 配置文件
<!-- 配置登陆页面验证码请求 -->
<servlet>
<servlet-name>codeimage</servlet-name>
<!-- 下面为Codeimage的路径-->
<servlet-class>cn.cntomorrow.beidian.action.CodeImage</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>codeimage</servlet-name>
<!--如果访问不到加上项目名 /项目名 /codeimage -->
<url-pattern>/codeimage</url-pattern>
</servlet-mapping>
<!-- struts不被拦截的请求 -->
<filter>
<filter-name>sessionFilter</filter-name>
<filter-class>cn.cntomorrow.web.base.SessionFilter</filter-class>
<init-param>
<param-name>notCheckURLList</param-name>
<!--里面是不走struts配置文件的请求-->
<param-value>loginAction.do,exemptLogin.do,.js,.css,codeimage</param-value>
</init-param>
<init-param>
<param-name>redirectURL</param-name>
<param-value>/login.jsp</param-value>
</init-param>
</filter>
第五步 Session.Filter类 可以用我的,也可以自己写一个
package cn.cntomorrow.web.base;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import mt.framework.util.Logger;
import share.Share;
/**
* session过滤器
* @author: Lyan
*/
public class SessionFilter implements Filter {
protected FilterConfig filterConfig = null;
private String notCheckURL = null;
private String redirectURL = null;
/**
* 销毁操作
*/
public void destroy() {
notCheckURL = new String();
redirectURL = new String();
}
/**
* 判断session是否存在,不存在则跳转到登录页面
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException{
// Session属于HTTP范畴,所以ServletRequest对象需要先转换成HttpServletRequest对象
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession();
//如果访问的资源属于放行资源
if (this.checkRequestURIIntNotFilterList(req)
//或者是根路径
|| req.getRequestURI().length() == req.getContextPath().length() + 1) {
chain.doFilter(req, res);
} else {
// 如果session不为空,则可以浏览其他页面
if(session.getAttribute(Globals.KEY_USERINFO) != null){
chain.doFilter(req, res);
} else if(req.getRequestURI().indexOf("resourceReader") > -1){
chain.doFilter(req, res);
}else if(req.getRequestURI().indexOf("insertCustomSar") > -1){
chain.doFilter(req, res);
}else {
Logger.info("!!!!!!!!!!!!!session time out!!!!!!!!!");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html; charset=UTF-8");
String msg = Share.iAttr().getProperty("sessionTimeOut");
// 跳转到登陆页
res.getWriter().write(
"<script>alert('" + msg
+ "');window.top.location.href='"
+ req.getContextPath() + redirectURL
+ "'</script>");
}
}
}
/**
* 初始化filter,从web.xml中读取参数
*/
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
redirectURL = filterConfig.getInitParameter("redirectURL");
String notCheckURLListStr = filterConfig.getInitParameter("notCheckURLList");
notCheckURL = notCheckURLListStr;
}
/**
* 判断当前url是否可以访问
* @param request
* @return boolean
* @author: zhlong
* @version: 2013-3-20 下午04:01:40
*/
private boolean checkRequestURIIntNotFilterList(HttpServletRequest request) {
String uri = request.getRequestURI();
String str[] = notCheckURL.split(",");
for(int i = 0;i<str.length;i++){
if(uri.toLowerCase().contains(str[i].toLowerCase())){
return true;
}
}
//return uri.contains(notCheckURL);
return false;
}
}
到此也就完成了。