验证表单重复提交(防止钓鱼,密码加密,自定义标签,过滤器)
这是本工程所有包与类。
1.创建好登录、注册页面。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="http://www.chinasofti.com/token" prefix="csi" %> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'regist.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> </head> <body> <form action="regist" method="post"> 此处用到自定义标签 <csi:token/> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="注册"> </form> </body> </html>
<body> <form action="login" method="post"> 用户名:<input type="text" name="username" value="${user }"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="登录"> </form> </body> 这里是登录界面,主体内容
2.创建登录、注册的servlet类
package com.chinasofti.um.servlet; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.chinasofti.common.service.DomainProtectedService; import com.chinasofti.util.sec.Passport; public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); request.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); Passport passport = new Passport(); DomainProtectedService dps = new DomainProtectedService(); if (dps.isFromSameDomain()) { String username = request.getParameter("username"); String password = passport.md5(request.getParameter("password")); HttpSession session = request.getSession(); session.setAttribute("user", username); try { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/ordersys", "root", "1"); PreparedStatement pstmt = conn .prepareStatement("select * from user_info where username=?"); pstmt.setString(1, username); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { String passdb = rs.getString(3); if (passdb.equals(password)) { out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.println("登录成功!"); out.println(" </BODY>"); out.println("</HTML>"); } else { out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.println("密码错误!"); out.println(" </BODY>"); out.println("</HTML>"); } } else { out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.println("用户名错误!"); out.println(" </BODY>"); out.println("</HTML>"); } } catch (Exception e) { e.printStackTrace(); } }else{ out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.println("非法登录!"); out.println(" </BODY>"); out.println("</HTML>"); } out.flush(); out.close(); } }
package com.chinasofti.um.servlet; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.chinasofti.um.common.taglib.TokenTag; import com.chinasofti.util.sec.Passport; public class RegistServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); request.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); Passport passport = new Passport(); //判断令牌是否有效 if(TokenTag.isTokenValid()){ String username = request.getParameter("username"); //String password = request.getParameter("password"); String password = passport.md5(request.getParameter("password")); //通过UUID来生产唯一的用户ID,作为主键 String userID = UUID.randomUUID().toString(); try { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/ordersys", "root", "1"); PreparedStatement pstmt = conn.prepareStatement("insert into user_info values(?,?,?)"); pstmt.setString(1, userID); pstmt.setString(2, username); pstmt.setString(3, password); pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } //释放令牌 TokenTag.releaseToken(); out.println("注册成功!<br>"); }else{ out.println("令牌无效,非法注册!<br>"); } } }
3.创建防止表单重复提交的标签
package com.chinasofti.um.common.taglib; import java.io.IOException; import java.util.UUID; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; import com.chinasofti.um.common.httprequest.HttpRequestContext; /** * <p> * Title:TokenTag * </p> * <p> * Decription:防止表单重复提交的标签 * </p> * @author csi * @version 1.0 * */ public class TokenTag extends TagSupport { /** * 令牌属性:在session中的属性名 */ public static final String TOKEN_SESSION_ATTR_NAME = "SUBMIT_TOKEN_ATTR_NAME_SESSION"; /** * 令牌属性:在request中的属性名 */ public static final String TOKEN_REQUEST_ATTR_NAME = "SUBMIT_TOKEN_ATTR_NAME_REQUEST"; /** * 判断当前请求中是否包含合法令牌值的方法 * * @return true-请求中包含合法令牌值,false-请求中不包含合法令牌值 */ public static boolean isTokenValid(){ //获取请求中的令牌值 String requestToken = HttpRequestContext.getRequest(). getParameter(TOKEN_REQUEST_ATTR_NAME); //获取会话中的令牌值 Object sessionToken = HttpRequestContext.getRequest().getSession(). getAttribute(TOKEN_SESSION_ATTR_NAME); System.out.println(requestToken + "--->" + sessionToken); //判断令牌是否合法 return sessionToken != null && sessionToken.toString().equals(requestToken); } /** * 释放会话中令牌值 */ public static void releaseToken(){ HttpRequestContext.getRequest().getSession(). setAttribute(TOKEN_SESSION_ATTR_NAME, ""); } @Override public int doEndTag() throws JspException { //利用UUID获取唯一的令牌值 String token = UUID.randomUUID().toString(); //在会话session中保存令牌值 pageContext.getSession().setAttribute(TOKEN_SESSION_ATTR_NAME, token); //创建表单令牌的HTML字符串 String tokenTag = "<input type=\"hidden\" name=\"SUBMIT_TOKEN_ATTR_NAME_REQUEST\" value=\"" + token + "\"/>"; try { //在页面中输出令牌域字符串 pageContext.getOut().print(tokenTag); } catch (IOException e) { e.printStackTrace(); } //本标签结束后继续执行页面的其他内容 return EVAL_PAGE; } @Override public int doStartTag() throws JspException { //跳过标签体 return SKIP_BODY; } }
4.创建过滤器(在处理每次请求前捕获请求,响应对象的过滤器)
package com.chinasofti.um.common.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.chinasofti.um.common.httprequest.HttpRequestContext; /** * 在处理每次请求前捕获请求,响应对象的过滤器 * @author csi * @version 1.0 * */ public class HttpRequestContextFilter implements Filter { private ServletContext context; @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //在ThreadLocal中共享本次请求、响应对象 HttpRequestContext.setHttpRequestContext((HttpServletRequest)request, (HttpServletResponse)response, context); chain.doFilter(request, response); } @Override /** * 过滤器初始化是执行的回调方法 * @param config * 过滤器配置对象,可以读取配置文件中的特殊的配置信息 */ public void init(FilterConfig config) throws ServletException { //使用过滤器配置对象获取ServletContext对象 context = config.getServletContext(); } }
5.创建 请求,响应,ServletContext对象的包装对象
package com.chinasofti.um.common.httprequest; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 请求,响应,ServletContext对象的包装对象 * @author csi * @version 1.0 * */ public class HttpRequestContext { /** * 请求对象 */ private HttpServletRequest request; /** * 响应对象 */ private HttpServletResponse response; /** * ServletContext对象 */ private ServletContext servletContext; /** * 构造器,构造包装对象 * @param request * 请求对象 * @param response * 响应对象 * @param servletContext * Servlet上下文对象 */ public HttpRequestContext(HttpServletRequest request,HttpServletResponse response, ServletContext servletContext){ //初始化request对象 this.request = request; //初始化response对象 this.response = response; //初始化ServletContext对象 this.servletContext = servletContext; } /** * ThreadLocal对象,用于在单一线程中共享数据 */ private static ThreadLocal<HttpRequestContext> currentContext = new ThreadLocal<HttpRequestContext>(); /** * 设置共享数据的方法 * @param request 请求对象 * @param response 响应对象 * @param servletContext ServletContext对象 */ public static void setHttpRequestContext(HttpServletRequest request,HttpServletResponse response, ServletContext servletContext){ //构造包装对象 HttpRequestContext context = new HttpRequestContext(request, response, servletContext); //在ThreadLocal中存放包装对象 currentContext.set(context); } /** * 获取请求对象 * @return 请求对象 */ public static HttpServletRequest getRequest() { return currentContext.get() == null ? null : currentContext.get().request; } /** * 获取响应对象 * @return 响应对象 */ public static HttpServletResponse getResponse() { return currentContext.get() == null ? null : currentContext.get().response; } /** * 获取ServletContext对象 * @return ServletContext对象 */ public static ServletContext getServletContext() { return currentContext.get() == null ? null : currentContext.get().servletContext; } }
6.创建 防止盗链、防止外站提交数据的服务对象
/** * Copyright 2015 ChinaSoft International Ltd. All rights reserved. */ package com.chinasofti.common.service; import javax.servlet.http.HttpServletRequest; import com.chinasofti.um.common.httprequest.HttpRequestContext; /** * <p> * Title: DomainProtectedService * </p> * <p> * Description: 防止盗链、防止外站提交数据的服务对象 * </p> * <p> * Copyright: Copyright (c) 2015 * </p> * <p> * Company: ChinaSoft International Ltd. * </p> * * @author etc * @version 1.0 */ public class DomainProtectedService { /** * 判定是否盗链或是否外站提交数据的方法 * * @return 判定结果,ture表示本站合法请求,false表示外站请求 * */ public boolean isFromSameDomain() { // 获取本次的请求对象 HttpServletRequest request = HttpRequestContext.getRequest(); // 获取本站的context root String path = request.getContextPath(); // 获取本站截至到context root的域名信息 String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; // 获取上一个页面的地址 String fromUrl = request.getHeader("referer"); System.out.println(fromUrl + "--->" + basePath); // 判定是否外站请求并返回结果 return fromUrl != null && fromUrl.startsWith(basePath) ? true : false; } }
7.创建一个算法对密码进行加密
包含几种加密方法 package com.chinasofti.util.sec; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; /** * <p> * Title: Passport * </p> * <p> * Description:自定义的可逆加密算法,主要用于非超大容量的字符串加密,可以生成网络传输数据的令牌,不同时间执行加密算法获取到的加密结果不同 * </p> * <p> * Copyright: Copyright (c) 2015 * </p> * <p> * Company: ChinaSoft International Ltd. * </p> * * @author etc * @version 1.0 */ public class Passport { public Passport() { // TODO 自动生成构造函数存根 } /** * @param args */ public static void main(String[] args) { // TODO 自动生成方法存根 Passport passport = new Passport(); // String txt = "中文文本"; // String key = "chinasofti"; // String jia_str = passport.passport_encrypt(txt, key); // String jie_str = passport.passport_decrypt(jia_str, key); // System.out.println("加密函数测试:" + jia_str); // System.out.println("解密函数测试:" + jie_str); System.out.println(passport.md5("admin")); } /** * Md5加密 * * @param x * 需要加密的字符串 * @return md5加密结果 * @throws Exception */ public String md5(String x) { // 获取摘要工具 MessageDigest m = null; try { // MD5摘要工具 m = MessageDigest.getInstance("MD5"); // 更新被文搞描述的位元组 m.update(x.getBytes("UTF8")); // 捕获不支持摘要异常 } catch (NoSuchAlgorithmException e) { // 创建一个MD5消息文搞 的时候出错 e.printStackTrace(); // 捕获不支持字符集异常 } catch (UnsupportedEncodingException e) { // 更新被文搞描述的位元组 的时候出错 e.printStackTrace(); } // 最后更新使用位元组的被叙述的排列,然后完成文摘计算 byte s[] = m.digest(); // System.out.println(s); // 输出加密后的位元组 // 创建结果字符串缓冲 StringBuilder result = new StringBuilder(""); // 遍历文摘 for (int i = 0; i < s.length; i++) { // 进行十六进制转换 result.append(Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00) .substring(6)); } // 返回加密结果 return result.toString(); } /** * 本方法将字符串以 MIME BASE64 编码。此编码方式可以让中文字或者图片也能在网络上顺利传输。在 BASE64 * 编码后的字符串只包含英文字母大小写、阿拉伯数字、加号与反斜线,共 64 个基本字符,不包含其它特殊的字符, 因而才取名 * BASE64。编码后的字符串比原来的字符串长度再加 1/3 左右。更多的 BASE64 编码信息可以参考 RFC2045 文件之 6.8 节 * * @param txt * 等待编码的原字串 * @return 编码后的结果 */ public String base64_decode(String txt) { // 定义编码器 BASE64Decoder base64_decode = new BASE64Decoder(); // 定义结果字符串 String str = ""; try { // 获取加密结果 str = new String(base64_decode.decodeBuffer(txt)); } catch (IOException e) { // 如果有异常则输出异常信息 e.printStackTrace(); } // 返回编码结果 return str; } /** * Base64编码的方法 * * @param txt * 要编码的字符串 * @return 编码结果 * */ public String base64_encode(String txt) { // 创建编码器 BASE64Encoder base64_encode = new BASE64Encoder(); // 返回编码结果 return base64_encode.encode(txt.getBytes()); } /** * Passport 加密方法 * * @param string * 等待加密的原字串 * @param string * 私有密匙(用于解密和加密) * * @return string 原字串经过私有密匙加密后的结果 */ public String passport_encrypt(String txt, String key) { // 创建随机工具 Random random = new Random(); // 使用随机数发生器产生 0~32000 的值 String rad = String.valueOf(random.nextInt(32000)); // 获取随机值的md5码 String encrypt_key = md5(rad); // 变量初始化 int ctr = 0; // 定义结果字符串缓冲 StringBuilder tmp = new StringBuilder(""); // 获取md5码的字符数组形式 char encrypt_key_char[] = encrypt_key.toCharArray(); // 获取初始文本的字符数组形式 char txt_char[] = txt.toCharArray(); // for 循环,$i 为从 0 开始,到小于 $txt 字串长度的整数 for (int i = 0; i < txt.length(); i++) { // 如果 $ctr = $encrypt_key 的长度,则 $ctr 清零 ctr = ctr == encrypt_key_char.length ? 0 : ctr; // $tmp 字串在末尾增加两位,其第一位内容为 $encrypt_key 的第 $ctr 位, // 第二位内容为 $txt 的第 $i 位与 $encrypt_key 的 $ctr 位取异或。然后 $ctr = $ctr + 1 char tmp1 = txt_char[i]; // 编码字符 char tmp4 = encrypt_key_char[ctr]; // 编码第二个字符 char tmp2 = encrypt_key_char[ctr++]; // 进行位运算 char tmp3 = (char) (tmp1 ^ tmp2); // 添加结果数据 tmp.append(tmp4 + "" + tmp3); } // 返回结果,结果为 passport_key() 函数返回值的 base65 编码结果 return base64_encode(passport_key(tmp.toString(), key)); } /** * Passport 解密方法 * * @param string * 加密后的字串 * @param string * 私有密匙(用于解密和加密) * * @return string 字串经过私有密匙解密后的结果 */ public String passport_decrypt(String txt, String key) { // $txt 的结果为加密后的字串经过 base64 解码,然后与私有密匙一起, // 经过 passport_key() 函数处理后的返回值 txt = passport_key(base64_decode(txt), key); // 变量初始化 StringBuilder tmp = new StringBuilder(""); // 获取字符串数组形式 char txt_char[] = txt.toCharArray(); // for 循环,$i 为从 0 开始,到小于 $txt 字串长度的整数 for (int i = 0; i < txt.length(); i++) { // $tmp 字串在末尾增加一位,其内容为 $txt 的第 $i 位, // 与 $txt 的第 $i + 1 位取异或。然后 $i = $i + 1 tmp.append((char) (txt_char[i] ^ txt_char[++i])); } // 返回 $tmp 的值作为结果 return tmp.toString(); } /** * Passport 密匙处理方法 * * @param string * 待加密或待解密的字串 * @param string * 私有密匙(用于解密和加密) * * @return string 处理后的密匙 */ String passport_key(String txt, String encrypt_key) { // 将 $encrypt_key 赋为 $encrypt_key 经 md5() 后的值 encrypt_key = md5(encrypt_key); // 变量初始化 int ctr = 0; // 创建结果字符串缓冲 StringBuilder tmp = new StringBuilder(""); // 获取md5码字符数组形式 char encrypt_key_char[] = encrypt_key.toCharArray(); // 获取原文本字符数组表现形式 char txt_char[] = txt.toCharArray(); // for 循环,$i 为从 0 开始,到小于 $txt 字串长度的整数 for (int i = 0; i < txt.length(); i++) { // 如果 $ctr = $encrypt_key 的长度,则 $ctr 清零 ctr = ctr == encrypt_key.length() ? 0 : ctr; // $tmp 字串在末尾增加一位,其内容为 $txt 的第 $i 位, // 与 $encrypt_key 的第 $ctr + 1 位取异或。然后 $ctr = $ctr + 1 char c = (char) (txt_char[i] ^ encrypt_key_char[ctr++]); // 追加结果 tmp.append(c); } // 返回 $tmp 的值作为结果 return tmp.toString(); } }
8.配置xml文件
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name></display-name> <filter> <filter-name>httpRequestContext</filter-name> <filter-class>com.chinasofti.um.common.filter.HttpRequestContextFilter</filter-class> </filter> <filter-mapping> <filter-name>httpRequestContext</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>RegistServlet</servlet-name> <servlet-class>com.chinasofti.um.servlet.RegistServlet</servlet-class> </servlet> <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.chinasofti.um.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RegistServlet</servlet-name> <url-pattern>/regist</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
9.配置自定义标签的.tld文件
<?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1"> <description>csi 1.0 token library</description> <display-name>token core</display-name> <tlib-version>1.2</tlib-version> <short-name>csi</short-name> <uri>http://www.chinasofti.com/token</uri> <tag> <name>token</name> <tag-class>com.chinasofti.um.common.taglib.TokenTag</tag-class> <body-content>empty</body-content> </tag> </taglib>
10.此处用到了数据库,我们将数据库的jar包放到lib文件下,配置好这几个文件,就ok了,
验证表单重复提交(防止钓鱼,密码加密,自定义标签,过滤器)等,