简单的javaweb项目!
测试:
怎么使用?
只有用户名、密码 验证的可以参考后边的demo (jquery)
改进 后的可以参考后边“改进”部分的“使用方法 ”(jquery)
注册 接口的使用可以参考后边的“–注册–”部分的”使用方法 “(vue)
注意,本文代码较多,建议使用PC观看,可以点击左侧的目录快速跳转
介绍
前端页面发送用户名 和密码 两个参数给服务器,服务器端通过访问数据库中的数据,验证账号密码是否正确,最后返回验证状态 和用户名 两个参数给前端页面。
更新:添加注册API 。
环境
开发工具:IDEA、Maven
前端:jsp、Ajax
– 登陆验证界面(异步)
后端:centos7、Javaweb、Tomcat、mysql
– 提供登陆验证接口
前后端规定好:前端发 送两个参数 过来user 、passwd ,要验证的用户名和密码。后端处理后(访问数据库进行验证),返回 一个JSON类型的数据,其中包括两个参数status 、user ,
验证的结果(true,验证通过。false,验证不通过)和用户名。
我们在IDEA中使用Maven模板创建一个web应用程序。这里web应用程序项目名设置为Login(关于如何创建,可以访问这里 )。(失误,感觉这个名字起的不是很好,例如:interface就还行)
- - 登陆 - -
前端 页面
使用一个from表单进行搭建,没有设置action、method属性。数据提交和处理服务器返回的数据,通过给按钮添加监听器结合Ajax实现。
文本输入框,设置id名,通过let user = document.getElementById("user");
user.value
获取框中的文本(数据)
通过Ajax,使用POST请求提交数据给服务器(后端接口)
通过Ajax的回调函数onreadystatechange
来处理服务器返回的数据
为了方便调试,服务器返回的数据直接显示在页面上(form表单前边的h1、h2标签)
在这部分使用的是原生的Ajax,在后边的demo中使用了jquery封装的Ajax。
前端页面搭建,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>login</title> </head> <body> <h1></h1> <h2></h2> <form style="width: 500px;height: 200px" > <fieldset> <legend>登陆</legend> 用户名:<input type="text" id="user" ><br> 密码:<input type="password" id="passwd" ><br> <input type="button" value="提交" onclick="mysubmit()" > </fieldset> </form> </body> <script type="text/javascript" > function mysubmit () { let user = document.getElementById("user" ); let passwd = document.getElementById("passwd" ); if (user.value.length != 0 && passwd.value.length != 0 ){ document.querySelector("h2" ).innerHTML="尝试发送数据到服务器!!!" ; var xmlhttp; if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } else { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP" ); } xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200 ) { document.querySelector("h1" ).innerHTML=xmlhttp.responseText; document.querySelector("h2" ).innerHTML="收到服务器的响应!!!" ; } } xmlhttp.open("POST" ,"${pageContext.request.contextPath}/login" ,true ); xmlhttp.setRequestHeader("Content-type" ,"application/x-www-form-urlencoded" ); xmlhttp.send("user=" +user.value+"&passwd=" +passwd.value); } } </script> </html>
Ajax部分 :
${pageContext.request.contextPath}
为EL表达式,pageContext
为JSP页面的内置对象 ,用于获取当前页面(文件)所处的目录(路径)。前后端分离的话,这里需要改成后端接口的访问地址(本案例中,后端接口访问地址为http://qsdbl.site:8080/Login/login
,这里post请求的地址更改为这个也可以。
注意:我一开始没有考虑到跨域问题(CDR错误),在本博客后边的demo 中有提供修改后的源码,需要了解怎么解决跨域问题可以查看这里 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
var xmlhttp;if (window .XMLHttpRequest){ xmlhttp=new XMLHttpRequest(); } else { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP" ); } xmlhttp.onreadystatechange=function ( ) { if (xmlhttp.readyState==4 && xmlhttp.status==200 ) { document .querySelector("h1" ).innerHTML=xmlhttp.responseText; document .querySelector("h2" ).innerHTML="收到服务器的响应!!!" ; } } xmlhttp.open("POST" ,"${pageContext.request.contextPath}/login" ,true ); xmlhttp.setRequestHeader("Content-type" ,"application/x-www-form-urlencoded" ); xmlhttp.send("user=" +user.value+"&passwd=" +passwd.value);
open方法
的第三个参数设置为true
,意思是异步处理请求。服务器响应后,执行 onreadystatechange
事件中定义的函数(若设置为false,则不写onreadystatechange事件对应的函数)。
setRequestHeader
为固定设置。send
方法中,填写要发送给服务器处理的数据,键值对的形式。
Ajax教程,见菜鸟教程
后端
准备工作
阿里云ECS的域名为:qsdbl.site
mysql数据库
在阿里云ECS,Centos7上配置好MySql数据库。MySQL数据库的默认端口为3306,所以访问地址为qsdbl.site:3306
。(数据库安装和建库建表详细过程 访问这里:传送门 )
Tomcat
在阿里云ECS,Centos7上配置好Tomcat服务。Tomcat服务的默认端口为8080,所以访问地址为http://qsdbl.site:8080
。(Tomcat服务的详细安装过程访问这里:传送门 )
编写后端接口
数据库 操作DAO模式
使用DAO(Data Access Object)模式 操作数据库。将数据库的操作部分提取出来,封装成一个可被调用的程序单元,提高代码的重用性。使程序专注于处理业务逻辑。(关于DAO,可以看看这两篇文章:文章1 、文章2 )
需要导入两个第三方jar包,Mysql连接驱动和C3P0数据源。使用C3P0数据源,可以将连接池和连接池管理合二为一,提高访问数据库的效率。(整个案例中使用到的所有依赖,见博客最后边的源码)
1 2 3 4 5 6 7 8 9 10 11 12
<dependency > <groupId > com.mchange</groupId > <artifactId > c3p0</artifactId > <version > 0.9.5.5</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.22</version > </dependency >
在项目中创建包com.qsdbl.connect;
,放与操作数据库相关的类。
DAO父类
创建类BaseDao,作为DAO的父类。用于获得数据库连接对象 ,有两个构造方法 ,一定要提供的实参 有:
数据库所在服务器的IP地址或域名 ,下边的address属性
所要操作的数据库名 ,下边的database_name属性
有权限操作该数据库的账户与密码 ,下边的user属性和passwd属性
源码如下:
若出现中文乱码问题,可查看这篇博客 ,在下边代码45行处的setJdbcUrl中添加一些参数。
?useUnicode=true&characterEncoding=utf-8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
package com.qsdbl.connect;import com.mchange.v2.c3p0.ComboPooledDataSource;import java.beans.PropertyVetoException;import java.sql.Connection;import java.sql.SQLException;public class BaseDao { private String address; private String database_name; private String user; private String passwd; private int maxPoolSize = 40 ; private int minPoolSize = 2 ; private int initialPoolSize = 10 ; private int maxStatements = 180 ; public ComboPooledDataSource ds; public BaseDao (String address,String database_name,String user,String passwd) throws PropertyVetoException { this .address = address; this .database_name = database_name; this .user = user; this .passwd = passwd; init(); } public void init () throws PropertyVetoException { ds = new ComboPooledDataSource(); ds.setDriverClass("com.mysql.cj.jdbc.Driver" ); ds.setJdbcUrl(String.format("jdbc:mysql://%s:3306/%s" ,address,database_name)); ds.setUser(user); ds.setPassword(passwd); ds.setMaxPoolSize(maxPoolSize); ds.setMinPoolSize(minPoolSize); ds.setInitialPoolSize(initialPoolSize); ds.setMaxStatements(maxStatements); } public Connection getConnection () throws SQLException { return ds.getConnection(); } }
DAO子类
根据业务需要定义DAO的不同子类,操作数据库。本案例中,要操作的数据表 为数据库qsdbl中的account表。
类Table_account,实现对数据库的增删改查,继承类BaseDao。(不同的表,定义不同的DAO子类)
一些说明:
对象con、pstmt可以放到try()-catch语句的()中,try()-catch中的代码 一般是对资源的申请,如果出了异常会被关闭(释放)。 –
下边没有使用这种结构
catch中使用SQLException对象.getErrorCode()
,可获得数据查询的错误代码(可以看到查询数据库过程中到底是出了什么错误)
PreparedStatement对象继承自Statement类,将参数化的SQL语句发送到数据库。Statement对象发送的语句是一个功能明确而具体的语句。PreparedStatement与编译预处理有关,编译预处理是为了提高数据库存取的效率。(PreparedStatement可防止sql注入 。可看看这篇博客 )
?为占位符,占位符的序号从左到右从1开始,具体的参数通过 类的setXX方法设置
Account对象,一个对象对应数据库中的account表的一行数据
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
package com.qsdbl.connect;import java.beans.PropertyVetoException;import java.util.Objects;import java.sql.*;public class Table_account extends BaseDao { private String table = "account" ; public void setTable (String table) { this .table = table; } public Table_account () throws PropertyVetoException { super ("qsdbl.site" ,"qsdbl" ,"jack" ,"12345Qsdbl--" ); } public String add (Account account) throws SQLException { String sql = String.format("insert into %s ( user,passwd,type,mail) values (?,?,?,?);" ,table); Connection con = null ; PreparedStatement pstmt = null ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1 , account.getUser()); pstmt.setString(2 , account.getPasswd()); pstmt.setString(3 , account.getType()); pstmt.setString(4 , account.getMail()); pstmt.executeUpdate(); } catch (SQLException throwables) { throwables.printStackTrace(); return Integer.toString(throwables.getErrorCode()); }finally { if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } return "success" ; } public String del (String username) throws SQLException { if (!Objects.equals(findByName(username),"" )){ Account account = new Account(); account.setUser(username); account.setType("deprecated" ); return alterType(account); } return null ; } public String alterPasswd (Account account) throws SQLException { String sql = String.format("update %s set passwd=? where user=?; " ,table); Connection con = null ; PreparedStatement pstmt = null ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1 , account.getPasswd()); pstmt.setString(2 , account.getUser()); pstmt.executeUpdate(); } catch (SQLException throwables) { return Integer.toString(throwables.getErrorCode()); }finally { if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } return "success" ; } public String alterType (Account account) throws SQLException { String sql = String.format("update %s set type=? where user=?; " ,table); Connection con = null ; PreparedStatement pstmt = null ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1 , account.getType()); pstmt.setString(2 , account.getUser()); pstmt.executeUpdate(); } catch (SQLException throwables) { throwables.printStackTrace(); return Integer.toString(throwables.getErrorCode()); }finally { if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } return "success" ; } public String alterTypePasswd (Account account) throws SQLException { String sql = String.format("update %s set type=?,passwd=? where user=?; " ,table); Connection con = null ; PreparedStatement pstmt = null ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1 , account.getType()); pstmt.setString(2 , account.getPasswd()); pstmt.setString(3 , account.getUser()); pstmt.executeUpdate(); } catch (SQLException throwables) { return Integer.toString(throwables.getErrorCode()); }finally { if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } return "success" ; } public Account findByName (String username) throws SQLException { Account account = new Account(); String sql = String.format("select * from %s where user=?; " ,table); Connection con = null ; PreparedStatement pstmt = null ; ResultSet rst = null ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1 , username); rst = pstmt.executeQuery(); if (rst.next()){ account.setId(Integer.parseInt(rst.getString("id" ))); account.setUser(rst.getString("user" )); account.setPasswd(rst.getString("passwd" )); account.setType(rst.getString("type" )); account.setTime(rst.getDate("time" )); account.setTimeup(rst.getTimestamp("timeup" )); } } catch (SQLException throwables) { throwables.printStackTrace(); return null ; }finally { if (rst != null ){ rst.close(); } if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } return account; } public int find_Sum (Account account) throws SQLException { String sql = String.format("select count(*) from %s;" ,table); Connection con = null ; PreparedStatement pstmt = null ; ResultSet rst = null ; int sum = 0 ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); rst = pstmt.executeQuery(); if (rst.next()){ sum = rst.getInt(1 ); } } catch (SQLException throwables) { return throwables.getErrorCode(); }finally { if (rst != null ){ rst.close(); } if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } return sum; } public String find_name (String username) throws SQLException { String sql = String.format("select count(*) from %s where user=?;" ,table); Connection con = null ; PreparedStatement pstmt = null ; ResultSet rst = null ; int sum = 0 ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1 ,username); rst = pstmt.executeQuery(); if (rst.next()){ sum = rst.getInt(1 ); } } catch (SQLException throwables) { return "error" ; }finally { if (rst != null ){ rst.close(); } if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } if (sum > 0 ){ return "exist" ; }else { return "none" ; } } public boolean check_passwd (Account account) throws SQLException { String sql = String.format("select count(*) from %s where user=? && passwd=?;" ,table); Connection con = null ; PreparedStatement pstmt = null ; ResultSet rst = null ; int sum = 0 ; try { con = ds.getConnection(); pstmt = con.prepareStatement(sql); pstmt.setString(1 ,account.getUser()); pstmt.setString(2 ,account.getPasswd()); rst = pstmt.executeQuery(); if (rst.next()){ sum = rst.getInt(1 ); } } catch (SQLException throwables) { throwables.printStackTrace(); return false ; }finally { if (rst != null ){ rst.close(); } if (pstmt != null ){ pstmt.close(); } if (con != null ){ con.close(); } } if (sum > 0 ){ return true ; }else { return false ; } } }
本案例中用到的只是查询数据库的操作,没有考虑到事务回滚。关于事务,可以看看这篇博客 复习一下。
bean类
Bean类,JavaBean是一种Java语言写成的可重用组件。(pojo ,简单的Java对象) 针对不同的数据,设计不同的bean类,该类对应数据库中的account表的一行数据(一个account对象),用于存储一个account实例(一个账户)。类Account中的各个属性对应account数据表的各个列名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
package com.qsdbl.connect;import java.sql.Timestamp;import java.sql.Date;public class Account { private int id; private String user; private String passwd; private String type = "common" ; private Date time; private Timestamp timeup; public Account () { } public int getId () { return id; } public void setId (int id) { this .id = id; } ...(get、set方法省略) public void setTimeup (Timestamp timeup) { this .timeup = timeup; } }
验证模块
bean类Account的对象用于保存一个账户信息。使用DAO子类可以对该账户信息进行增删改查等操作。
编写类CheckLogin ,用于验证浏览器发送过来的数据(账号、密码),并返回验证结果和用户名。
将数据转换为json类型,我们需要使用到一个第三方的jar包:(整个案例中使用到的所有依赖,见博客最后边的源码)
1 2 3 4 5 6
<dependency > <groupId > top.jfunc.common</groupId > <artifactId > converter</artifactId > <version > 1.8.0</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
package com.qsdbl.login;import com.alibaba.fastjson.JSONObject;import com.qsdbl.connect.Account;import com.qsdbl.connect.Table_account;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.beans.PropertyVetoException;import java.io.IOException;import java.io.PrintWriter;import java.util.Objects;public class CheckLogin extends HttpServlet { @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); resp.setCharacterEncoding("utf-8" ); resp.setContentType("application/json; charset=utf-8" ); boolean status = false ; String user = req.getParameter("user" ); String passwd = req.getParameter("passwd" ); if (!Objects.equals(user,null ) && !Objects.equals(passwd,null )){ try { Account account = new Account(); account.setUser(user); account.setPasswd(passwd); status = new Table_account().check_passwd(account); } catch (PropertyVetoException e) { e.printStackTrace(); } } JSONObject o = new JSONObject(); o.put("user" , user); o.put("status" ,status); PrintWriter writer = resp.getWriter(); writer.write( o.toString()); writer.flush(); writer.close(); } @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } }
该Servlet程序返回的数据是json类型,包括用户名和验证的状态。
最后不要忘了到web.xml中注册。(当然使用注解配置也是可以的)。注册的请求路径为/login
,服务器的地址为qsdbl.site
,将该web应用程序部署到服务器上后,该Servlet程序(后端接口)访问地址为http://qsdbl.site:8080/Login/login
,该web应用程序项目名为Login,大写。(关于注册Servlet,可以访问这里 复习)
1 2 3 4 5 6 7 8
<servlet > <servlet-name > login</servlet-name > <servlet-class > com.qsdbl.login.CheckLogin</servlet-class > </servlet > <servlet-mapping > <servlet-name > login</servlet-name > <url-pattern > /login</url-pattern > </servlet-mapping >
总结一下编写该后端接口可能需要用到的maven依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
<dependency > <groupId > top.jfunc.common</groupId > <artifactId > converter</artifactId > <version > 1.8.0</version > </dependency > <dependency > <groupId > com.mchange</groupId > <artifactId > c3p0</artifactId > <version > 0.9.5.5</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.22</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > servlet-api</artifactId > <version > 2.5</version > </dependency > <dependency > <groupId > javax.servlet.jsp</groupId > <artifactId > javax.servlet.jsp-api</artifactId > <version > 2.3.3</version > </dependency > <dependency > <groupId > javax.servlet.jsp.jstl</groupId > <artifactId > jstl-api</artifactId > <version > 1.2</version > </dependency > <dependency > <groupId > taglibs</groupId > <artifactId > standard</artifactId > <version > 1.1.2</version > </dependency >
部署
将制作好的web应用程序放到服务器上Tomcat的webapps文件夹下即可。
点击工具栏的绿色按钮,运行web应用程序。在左侧我们可以看到有一个target文件夹生成,里边的web应用程序名.war
就是编译生成的文件,还有一个与web应用程序同名的文件夹,war包解压后就是该文件。所以我们只需要将与web应用程序同名的文件夹拷贝到服务器上即可。(也可以将war包拷贝出来,解压后再放到服务器上)
这里web应用程序名为Login
,服务器域名为qsdbl.site
,Tomcat服务的默认端口号为8080
,所以放到Tomcat服务器上的webapps文件夹下之后,我们可以通过地址http://qsdbl.site:8080/Login
访问到前边编写的前端页面 。
运行结果
注意:需要了解怎么解决跨域问题可以查看这里
注意,本案例没有解决Tomcat的https问题,暂时只能使用http请求。
更新:可以使用http或https发起请求
demo
该登陆验证接口怎么使用可以参考下边这个demo。测试页面:传送门 。点这里 查看我提供的一些账户。需要添加更多的账户可以通过关于页面 的联系方式联系我。
API地址:http://qsdbl.site:8080/Login/login
需要携带的参数有:user、passwd。分别为用户名、密码。
服务器返回的jsonp数据中,有两个参数:status、user。分别为验证状态、用户名。验证状态status为true说明form表单中输入的用户名和密码正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
<!DOCTYPE html > <html > <head > <title > login</title > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" /> <body > <form id ="loginForm" style ="width: 500px;height: 200px" > <fieldset > <legend > 登陆</legend > 用户名:<input type ="text" name ="user" > <br > 密码:<input type ="password" name ="passwd" > <br > <input type ="button" value ="提交" onclick ="submit_data()" > </fieldset > </form > </body > <script src ="http://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js" > </script > </head > <script type ="text/javascript" > function submit_data () { var formParam = $("#loginForm" ).serialize(); $.ajax({ async :false , url:"http://qsdbl.site:8080/Login/login" , type:"post" , data:formParam, dataType:"jsonp" , success: function (data) { if (data.status == true ){ alert("登录成功" ); } else alert("账号或者密码错误" ); }, error:function (error) { alert("登录失败" ); } }); } </script > </html >
使用cdn方式引入jquery:
1
<script src="http://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js" ></script>
通过id绑定form表单中的数据,控件中的name设置为前边 约定好的字段名。使用jquery封装的Ajax发起请求。
1 2
var formParam = $("#loginForm" ).serialize();data:formParam
改进
解决跨域 问题
前端,接收后端数据使用的数据类型改为jsonp
后端,设置响应头允许跨域
添加验证码
RSA加密 。为了保证密码 能在网络上安全的进行传输,使用RSA 加密技术。前端加密,后端解密。
使用方法
新URI:http://qsdbl.site:8080/myinterface
或 https://qsdbl.site:8443/myinterface
以前的API还是可以使用的,旧URI还是http://qsdbl.site:8080/Login/
前端
使用jquery封装的Ajax发起请求。使用cdn方式引入jquery:
1
<script src="http://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js" ></script>
在加密之前需要跟服务器获取公钥,加解密也需要一定时间所以速度没有原来的快。
前端加密依赖,jsencrypt.min.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js" ></script> / /vue-cli npm install jsencrypt --save / /main.js导入 import {JSEncrypt} from 'jsencrypt' / /引入加密工具 / /main.js中定义一个加密函数并挂载到原型上 Vue.prototype.$encryptedData = function(publicKey, data) { / /new一个对象 let encrypt = new JSEncrypt(); / /设置公钥 encrypt.setPublicKey(publicKey); / /data是要加密的数据 let result = encrypt.encrypt(data); return result }
如果部署在配置了SSL的环境中,则需要将http更改为https。
前端页面修改如下 :
HTML
在原来的基础上添加了一个验证码输入框和显示验证码图片的img标签。(因为要对数据进行加密,所以不能使用demo那种方式获取输入框中的数据。name又改成了id,使用id获取数据)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<!DOCTYPE html > <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" /> <title > login</title > <body > <form id ="loginForm" style ="width: 550px;height: 200px" > <fieldset > <legend > 登陆</legend > 用户名:<input type ="text" id ="user" > <br > 密码:<input type ="password" id ="passwd" > <br > 验证码:<input type ="text" id ="verificode" > <img src ="" id ="verificode_img" style ="margin:0 10px" /> <a href ="#" style ="font-size: 10px" onclick ="changecode()" > 看不清?点这里切换一张</a > <br > <input type ="button" value ="提交" onclick ="submit_data()" > </fieldset > </form > </body > </head > </html >
JS
定义一些全局变量 :myuri、uuid、publicKey、timeout。
myuri:http://qsdbl.site:8080/myinterface
,在Tomcat服务器中部署后端接口程序的地址。Tomcat配置了SSL,所以这里可以使用http或https。本地测试则为http://localhost:8080/myinterface
。
uuid:存放服务器端发送来的uuid。由于要跨域使用,所以cookie、session不能使用,得使用一个标识来标志客户端。
publicKey:存放服务器端发送来的公钥。用于加密。
timeout:超时时间,单位秒。自动切换验证码的时间。
方法getKey(),用于获取服务器的公钥。
方法changecode(),用于获取uuid和自动切换验证码图片。
方法submit_data(),用于向服务器发起验证请求,当然这里也对数据进行加密。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
<script type="text/javascript" > let myuri = "http://qsdbl.site:8080/myinterface" ; let uuid = "" ; let publicKey = "" ; let timeout = 45 ; window .onload = function ( ) { getKey(); changecode(timeout); setInterval(function ( ) { changecode(timeout); },timeout*1000 ); } function changecode (timeout ) { ... } function submit_data ( ) { ... } function getKey ( ) { ... } </script>
方法getKey(),用于获取服务器的公钥。请求地址为myuri+"/getkey"
。服务器响应的数据中,参数publicKey为公钥。在接收到服务器响应的公钥后保存到全局变量publicKey中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
function getKey ( ) { $.ajax({ async :true , url: myuri+"/getkey" , type: "POST" , dataType: "jsonp" , success: function (data ) { if (data){ publicKey = data.publicKey; }; if (publicKey==null ){ alert("获取publicKey失败,请联系管理员!" ); return ; }; } }); }
方法changecode(),用于获取uuid和自动切换验证码图片。发起获取验证码请求时带上参数timeout 、uuid ,服务器返回图片URL和uuid。请求地址为myuri+"/verificode"
。服务器响应的数据中,参数vpath为验证码图片在服务器上的相对地址,需要加上myuri。还有一个参数uuid。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
function changecode (timeout ) { let verificode_img = document .getElementById("verificode_img" ); $.ajax({ async :true , url:myuri+"/verificode" , type:"POST" , data:{ "timeout" :timeout, "uuid" :uuid }, dataType:"jsonp" , success: function (resp ) { verificode_img.src = myuri+resp.vpath; uuid = resp.uuid; }, error:function (error ) { alert("验证码获取失败,请联系管理员!" ); } }); }
方法submit_data() ,用于向服务器发起验证请求,当然这里也对数据进行加密。请求地址为myuri+"/login"
。服务器响应的数据中,参数isVerificode为验证码是否正确,布尔值。参数status为用户名、密码验证状态,布尔值。参数user为用户名。
在发起请求时要带上参数user、passwd、verificode、uuid,分别是用户名、密码、验证码、uuid,各个参数经过加密后再发起请求。方法submit_data,当点击“提交”按钮时执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
function submit_data ( ) { let user = (document .getElementById("user" )).value; let passwd = (document .getElementById("passwd" )).value; let verificode = (document .getElementById("verificode" )).value; if (user.length!= 0 && passwd.length!= 0 && verificode.length==7 && publicKey.length!=0 ) { let jsencrypt = new JSEncrypt(); jsencrypt.setPublicKey(publicKey.trim()); user = jsencrypt.encrypt(user.trim()); passwd = jsencrypt.encrypt(passwd.trim()); verificode = jsencrypt.encrypt(verificode.trim()); let myuuid = jsencrypt.encrypt(uuid.trim()); $.ajax({ async :true , url:myuri+"/login" , type:"POST" , data:{ "user" :user, "passwd" :passwd, "verificode" :verificode, "uuid" :myuuid, }, dataType:"jsonp" , success: function (resp ) { if (resp.status === true && resp.isVerificode === true ){ alert("登录成功!" ); }else { if (resp.isVerificode === false ){ alert("验证码有误!" ); }else { alert("账号或者密码错误!" ); } } }, error:function (error ) { alert("登录失败" ); } }); } }
后端
RSA加密
前端向后端请求公钥(GetKey),对数据进行加密。后端程序接收到前端发来的经过加密的数据,使用私钥进行解密。
需要添加两个类。类RSAutils ,与类GetKey 。RSAutils,负责RSA 加密 相关的工作。生成公钥、解密等。GetKey是一个Servlet程序,负责生成公钥并发送给客户端。
用到的Maven依赖 如下:(整个案例中使用到的所有依赖,见博客最后边的源码)
1 2 3 4 5 6 7 8 9 10 11 12
<dependency > <groupId > commons-codec</groupId > <artifactId > commons-codec</artifactId > <version > 1.14</version > </dependency > <dependency > <groupId > org.bouncycastle</groupId > <artifactId > bcprov-jdk15on</artifactId > <version > 1.67</version > </dependency >
RSAutils :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
package com.qsdbl.util;import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher;import java.security.*;import java.security.interfaces.RSAPublicKey;public class RSAutils {private static final KeyPair keyPair = initKey();private static KeyPair initKey () { try { Provider provider =new org.bouncycastle.jce.provider.BouncyCastleProvider(); Security.addProvider(provider); SecureRandom random = new SecureRandom(); KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA" , provider); generator.initialize(1024 ,random); return generator.generateKeyPair(); } catch (Exception e) { throw new RuntimeException(e); } } public static String generateBase64PublicKey () { PublicKey publicKey = (RSAPublicKey)keyPair.getPublic(); return new String(Base64.encodeBase64(publicKey.getEncoded())); } public static String decryptBase64 (String string) { return new String(decrypt(Base64.decodeBase64(string.getBytes()))); } private static byte [] decrypt(byte [] byteArray) { try { Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider(); Security.addProvider(provider); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding" , provider); PrivateKey privateKey = keyPair.getPrivate(); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte [] plainText = cipher.doFinal(byteArray); return plainText; } catch (Exception e) { throw new RuntimeException(e); } } }
GetKey: web.xml中注册的请求路径为/getkey
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
package com.qsdbl.util;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class GetKey extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8" ); resp.setCharacterEncoding("utf-8" ); resp.setContentType("application/text; charset=utf-8" ); String publicKey = RSAutils.generateBase64PublicKey(); resp.getWriter().write(publicKey); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
RSA参考了这篇博客 。
servlet程序CheckLogin中的使用示例:
1 2 3
String user = RSAutils.decryptBase64(req.getParameter("user" ).trim()); String passwd = RSAutils.decryptBase64(req.getParameter("passwd" ).trim());
验证码
由于是跨域 使用,所以不能使用session保存客户端的验证码(数字)。为了解决这个问题,这里使用uuid来标识客户端,将分发给客户端的uuid和验证码映射在一起保存到VerifiCode对象中,再保存在ServletContext中。当客户端发起验证请求时带上uuid,就可以通过uuid在ServletContext中查找对应的验证码与客户端发过来的验证码进行对比,判断验证码是否填写正确。
编写验证码实体类VerifiCode(或叫做been、pojo),保存验证码和uuid。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
package com.qsdbl.util;public class VerifiCode { private String uuid; private String verificode; public VerifiCode (String uuid, String verificode) { this .uuid = uuid; this .verificode = verificode; } public String getUuid () { return uuid; } ... public void setVerificode (String verificode) { this .verificode = verificode; } }
Servlet程序VerifiCodeImg,处理客户端获取验证码请求。将生成的验证码图片保存在服务器上,返回验证码图片的URL和uuid。根据客户端的参数timeout,定时删掉保存在服务器上的验证码图片和保存在ServletContext中的验证码对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
package com.qsdbl.util;import com.alibaba.fastjson.JSONObject;import javax.imageio.ImageIO;import javax.imageio.stream.ImageOutputStream;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.awt.*;import java.awt.image.BufferedImage;import java.io.*;import java.util.*;import java.util.List;public class VerifiCodeImg extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uuid; if (req.getParameter("uuid" ) == null ){ return ; }else { uuid = req.getParameter("uuid" ); if (uuid.equals("" )){ uuid = UUID.randomUUID().toString(); } } int timeout = 30 ; if (req.getParameter("timeout" ) != null ){ timeout = Integer.valueOf(req.getParameter("timeout" )); } if (timeout <10 || timeout > 100 ){ return ; } String vcode = getRandom(); StringBuffer vcode_2=new StringBuffer(); for (int i=0 ;i<vcode.length();i++){ vcode_2.append(vcode.charAt(i)+" " ); } BufferedImage image = new BufferedImage(110 ,20 ,BufferedImage.TYPE_3BYTE_BGR); Graphics2D g = (Graphics2D)image.getGraphics(); g.setColor(Color.white); g.fillRect(0 ,0 ,110 ,20 ); g.setColor(Color.black); g.setFont(new Font(null ,Font.BOLD,15 )); g.drawString(vcode_2.toString(),0 ,17 ); for (int i=0 ;i<10 ;i++){ Random random=new Random(); int xBegin =random.nextInt(80 ); int yBegin =random.nextInt(20 ); int xEnd=random.nextInt(xBegin+30 ); int yEnd=random.nextInt(yBegin+30 ); g.setColor(getColor()); g.drawLine(xBegin, yBegin, xEnd, yEnd); } String vpath = saveImgToServer(image,timeout); saveCodeToServer(vcode,uuid,timeout); resp.setContentType("application/jsonp; charset=utf-8" ); JSONObject json = new JSONObject(); json.put("vpath" , vpath); json.put("uuid" ,uuid); String funString = req.getParameter("callback" ); PrintWriter writer = resp.getWriter(); writer.write(funString + "(" +json.toString()+")" ); writer.flush(); writer.close(); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } private String getRandom () { ... } private Color getColor () { ... } private void saveCodeToServer (final String vcode, final String uuid, final int timeout) { ... } private String saveImgToServer (BufferedImage img, final int timeout) throws IOException { ... }
方法getRandom,获取7位数的随机数,用于生成验证码图片。
1 2 3 4 5 6 7 8 9 10
private String getRandom () { String num = new Random().nextInt(1000000 )+"" ; StringBuffer supplement = new StringBuffer(); for (int i = 1 ; i <= 7 -num.length(); i++) { supplement.append("0" ); } num = supplement.toString() + num; return num; }
方法getColor,获取随机颜色,用于生成干扰线条。
1 2 3 4 5 6 7 8
private Color getColor () { Random random=new Random(); int r =random.nextInt(256 ); int g =random.nextInt(256 ); int b =random.nextInt(256 ); return new Color(r,g,b); }
方法saveCodeToServer ,保存验证码和uuid到ServletContext中,并定时删掉。将验证码和uuid保存在验证码对象VerifiCode中,将验证码对象发到一个ArrayList集合中,最后再将该集合添加到ServletContext中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
private void saveCodeToServer (final String vcode, final String uuid, final int timeout) { VerifiCode verifiCode = new VerifiCode(uuid,vcode); List<VerifiCode> verificodelist; final ServletContext servletContext = this .getServletContext(); if (servletContext.getAttribute("verificode" ) == null ){ verificodelist = new ArrayList<>(); }else { verificodelist = (List)servletContext.getAttribute("verificode" ); } boolean isUuid = false ; ListIterator iter = verificodelist.listIterator(); while (iter.hasNext()) { VerifiCode vc = (VerifiCode)iter.next(); if (Objects.equals(vc.getUuid(),uuid)){ vc.setVerificode(vcode); isUuid = true ; } } if (!isUuid){ verificodelist.add(verifiCode); } servletContext.setAttribute("verificode" ,verificodelist); new Thread(){ @Override public void run () { super .run(); try { sleep((timeout-3 )*1000 ); List<VerifiCode> verificodelist = (List)servletContext.getAttribute("verificode" ); ListIterator iter = verificodelist.listIterator(); while (iter.hasNext()) { VerifiCode vc = (VerifiCode)iter.next(); if (Objects.equals(vc.getUuid(),uuid)){ iter.remove(); } } servletContext.setAttribute("verificode" ,verificodelist); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); }
方法saveImgToServer,保存验证码图片到服务器上,返回验证码图片在服务器上的相对路径(相对于web应用程序)。这里保存验证码的目录为/img/vcodeImg
,保存的图片类型为jpg。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
private String saveImgToServer (BufferedImage img, final int timeout) throws IOException { String imgRelativePath = "/img/vcodeImg" ; String imgName = System.currentTimeMillis()+".jpg" ; String vcodeParentPath = this .getServletContext().getRealPath(imgRelativePath); File vppFile = new File(vcodeParentPath); if (!vppFile.exists()){ boolean ismk = vppFile.mkdirs(); } final String vcodePath = vcodeParentPath + "/" + imgName; InputStream inputStream = null ; ByteArrayOutputStream bs = new ByteArrayOutputStream(); try { ImageOutputStream imOut = ImageIO.createImageOutputStream(bs); ImageIO.write(img, "jpg" ,imOut); inputStream= new ByteArrayInputStream(bs.toByteArray()); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fos = new FileOutputStream(vcodePath); byte [] buffer = new byte [1024 *1024 ]; int len = 0 ; while ((len = inputStream.read(buffer)) > 0 ){ fos.write(buffer,0 ,len); } fos.flush(); fos.close(); inputStream.close(); new Thread(){ @Override public void run () { try { sleep((timeout)*1000 ); } catch (InterruptedException e) { e.printStackTrace(); } File vcodeImg = new File(vcodePath); if (vcodeImg.exists()){ vcodeImg.delete(); } } }.start(); return imgRelativePath+"/" +imgName; }
验证模块
登陆验证CheckLogin 的改动比较大,这里全部贴出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
package com.qsdbl.login;import com.alibaba.fastjson.JSONObject;import com.qsdbl.connect.Account;import com.qsdbl.connect.Table_account;import com.qsdbl.util.RSAutils;import com.qsdbl.util.VerifiCode;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.beans.PropertyVetoException;import java.io.IOException;import java.io.PrintWriter;import java.util.List;import java.util.ListIterator;import java.util.Objects;public class CheckLogin extends HttpServlet { @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!checkParameter(new String[]{"user" ,"passwd" ,"verificode" ,"uuid" },req)){ return ; } resp.setContentType("application/jsonp; charset=utf-8" ); boolean status = false ; boolean isVerificode = false ; String type = "" ; String user = RSAutils.decryptBase64(req.getParameter("user" ).trim()); String passwd = RSAutils.decryptBase64(req.getParameter("passwd" ).trim()); String user_verificode = RSAutils.decryptBase64(req.getParameter("verificode" ).trim()); String user_uuid = RSAutils.decryptBase64(req.getParameter("uuid" ).trim()); if (checkVcode(user_verificode,user_uuid)){ isVerificode = true ; try { Account account = new Account(); account.setUser(user); account.setPasswd(passwd); Account databaseAccount = new Table_account().findByName(account.getUser()); type = databaseAccount.getType(); if (type.equals("admin" ) || type.equals("common" )){ status = new Table_account().check_passwd(account); } } catch (Exception e) { e.printStackTrace(); } } JSONObject json = new JSONObject(); json.put("user" , user); json.put("type" ,type); json.put("status" ,status); json.put("isVerificode" ,isVerificode); String funString = req.getParameter("callback" ); PrintWriter writer = resp.getWriter(); writer.write(funString + "(" +json.toString()+")" ); writer.flush(); writer.close(); } @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } public static boolean checkVcode (String vcode,String uuid) { ... } public static boolean checkParameter (String[] params,HttpServletRequest req) { boolean status = true ; for (int i = 0 ; i < params.length; i++) { if (Objects.equals(req.getParameter(params[i]),null )){ status = false ; } } return status; } }
验证 客户端发来的验证码是否正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public static boolean checkVcode (String vcode,String uuid) { boolean status = false ; VerifiCode verifiCode = new VerifiCode(uuid,vcode); final List<VerifiCode> verificodelist; final ServletContext servletContext = this .getServletContext(); if (servletContext.getAttribute("verificode" ) != null ){ verificodelist = (List)servletContext.getAttribute("verificode" ); ListIterator iter = verificodelist.listIterator(); while (iter.hasNext()) { VerifiCode vc = (VerifiCode)iter.next(); if (Objects.equals(vc.getUuid(),uuid) && Objects.equals(vc.getVerificode(),vcode)){ status = true ; } } servletContext.setAttribute("verificode" ,verificodelist); } return status; }
跨域
编写过滤器CorsFilter,解决跨域问题。作用范围为整个web应用程序,允许跨域请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
package com.qsdbl.filter;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class CorsFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response =(HttpServletResponse)servletResponse; response.setHeader("Access-Control-Allow-Origin" , "*" ); response.setHeader("Access-Control-Allow-Methods" , "POST, GET, OPTIONS, DELETE" ); response.setHeader("Access-Control-Allow-Credentials" ,"true" ); response.setHeader("Access-Control-Allow-Headers" ,"*" ); response.setCharacterEncoding("utf-8" ); ((HttpServletRequest)servletRequest).setCharacterEncoding("utf-8" ); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy () { } }
web.xml
在web.xml文件 中进行如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
<servlet > <servlet-name > login</servlet-name > <servlet-class > com.qsdbl.login.CheckLogin</servlet-class > </servlet > <servlet-mapping > <servlet-name > login</servlet-name > <url-pattern > /login</url-pattern > </servlet-mapping > <servlet > <servlet-name > getkey</servlet-name > <servlet-class > com.qsdbl.util.GetKey</servlet-class > </servlet > <servlet-mapping > <servlet-name > getkey</servlet-name > <url-pattern > /getkey</url-pattern > </servlet-mapping > <servlet > <servlet-name > VerificationCode</servlet-name > <servlet-class > com.qsdbl.util.VerifiCodeImg</servlet-class > </servlet > <servlet-mapping > <servlet-name > VerificationCode</servlet-name > <url-pattern > /verificode</url-pattern > </servlet-mapping > <filter > <filter-name > cors</filter-name > <filter-class > com.qsdbl.filter.CorsFilter</filter-class > </filter > <filter-mapping > <filter-name > cors</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
测试
注意:测试的时候每次重启IDEA后要刷新浏览器页面更新公钥。
1 2 3 4 5 6 7 8
- - 同域 - - 访问部署在Tomcat中的web页面(验证模块部署在Tomcat的myinterface中): http://qsdbl.site:8080/myinterface/login_http.html https://qsdbl.site:8443/myinterface/login.html - - 跨域 - - 访问部署在Apache中的web页面(验证模块部署在Tomcat的myinterface中): https://www.qsdbl.site/private/myinterface/login.html
同域:
跨域:
- - 注册 - -
在原项目的基础上添加注册接口。注册时需要填写用户名、密码、邮箱,并通过邮件激活后才能进行登录,数据传输依然是使用RSA加密 。
注意:在新增注册模块时修复了前边案例中的一些bug(后端)和对部分代码进行优化,例如对前边的DAO部分进行了小小修改,本博客上的代码已更新不过图片上的就无法更改了。只是小修复不影响已部署的后端API的使用。
测试地址:传送门
使用方法
这里介绍一下注册接口的使用 。
注册账户
URI:http://qsdbl.site:8080/myinterface
或 https://qsdbl.site:8443/myinterfacet
API地址:/register
发起请求时携带参数有:user 、passwd 、verificode 、uuid 、mail
user,用户名
passwd,密码
verificode,验证码
uuid,用于标识客户端身份的uuid,由获取验证码图片的API获取
mail,邮箱地址。
服务器返回的jsonp数据中,参数有:isVerificode 、isExist 、status 、user
isVerificode,验证码验证状态。false-验证码填写错误
isExist,是否已经存在该账户。true-已存在该用户名,需要用户更改用户名重新注册
status,账户验证状态。true-密码与用户名正确
user,用户名
先判断isVerificode,验证码是否正确。再判断isExist是否存在该账户。因为isVerificode为false时不会进行账户查重操作,isExist默认为true
对登陆验证接口 进行了小小的修改(不影响之前的项目),新增加 了一个返回参数type 。如果验证失败status =false,可以查看一下type 的值,deprecated_new
– 新注册未进行激活操作,deprecated – 已被注销的账户不能进行登陆,最后才是用户名、密码输入不正确。
前端页面
这里的前端页面示例中改用vue 来完成数据交互部分,不使用前边登陆部分 的jquery。
改变
1、校验数据合法性 :前边的jquery案例中,是在提交数据的函数submit_data 中对数据合法性进行校验,这里使用H5的新特性required
– 必填。pattern
– 正则表达式,检查数据合法性。 在form表单的invalid事件中设置提示语,v-on:invalid.capture="formVali"
,formVali函数 设置输入框 自定义提示语。 (注意是使用事件捕获模式,原生js在addEventListener()中最后一个参数填true,vue中要使用“.capture”事件修饰符)
2、提交数据的触发方式 :前边的jquery案例中,提交数据 是使用普通button按钮添加点击事件监听器实现。这里是使用form表单的submit按钮。 通过监听form表单的submit事件在其中写ajax替代默认的提交行为。v-on:submit.prevent="mysubmit"
,mysubmit函数 在form的submit事件中,用ajax提交数据。 事件修饰符prevent,阻止默认事件,在这里的作用是提交事件不重载(刷新)页面
3、页面切换 :登陆页面与注册页面的切换 使用变量isLogin,判断当前页面应该显示登陆页面还是注册页面(true-登陆,false-注册)。 使用v-bind:class绑定样式,使用v-if:isLogin隐藏或显示相应的控件。见下边的例子: 只有登陆页面才有”忘记密码?”这个控件出现(点击第二个按钮切换两个页面)。 样式绑定:v-bind:class="{'Login_password':isLogin}"与v-bind:class="{
'Login_a': isLogin }"
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<span class ="massage" > 密码:</span > <input class ="kuang" v-bind:class ="{'Login_password':isLogin}" type ="password" required ="required" name ="passwd" pattern ="[\dA-Za-z]{5,16}" placeholder =" 长度为5-16个字符" v-model ="passwd" /> <a href ="" v-bind:class ="{ 'Login_a': isLogin }" v-if ="isLogin" > 忘记密码?</a > .form_bigbox form .Login_password{ width: 180px; margin-right: 10px; } .form_bigbox form .Login_a{ font-size: 12px; text-decoration: none; color: firebrick; }
4、加载动画 变量isAnimation,结合v-show决定控件是否隐藏。默认状态为隐藏。当点击登陆或注册按钮(且数据合法)向服务器提交数据时显示动画。当服务器响应后隐藏动画。(函数mysubmit中)
1 2 3
<div v-show ="isAnimation" v-on:click ="isAnimation=!isAnimation" class ="myanimation" > <canvas id ="myanimation" width ="100px" height ="100px" > </canvas > </div >
5、Ajax 使用vue的axios而不是使用jquery。 由于要解决跨域问题,需要使用jsonp类型的数据进行交互,vue不支持这种类型的数据故需要自己对数据进行处理。不过我在网了找了一个别人写好的方法,直接拿来使用即可(取自这篇博客 )。使用方法很简单,见源码中44-48行,94-103行的小例子。(或查看这里 )
源码
部分方法(函数)中的代码太长了,为了提高易读性我将它们单独贴在后边,点击左侧的标题可以快速定位过去
html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
<body > <div id ="app" > <div class ="form_bigbox" > <div class ="form_title" > <h2 > {{submit_btn}}账号</h2 > </div > <form v-on:submit.prevent ="mysubmit" v-on:invalid.capture ="formVali" > <span class ="massage" > 用户名:</span > <input class ="kuang" type ="text" required ="required" name ="name" pattern ="[-\w\u4E00-\u9FA5]{4,10}" placeholder =" 长度为4-10个字符" autofocus ="autofocus" v-model ="user" /> <br /> <br /> <span class ="massage" > 密码:</span > <input class ="kuang" type ="password" required ="required" name ="passwd" pattern ="[\dA-Za-z]{5,16}" placeholder =" 长度为5-16个字符" v-model ="passwd" /> <span v-if ="!isLogin" > <br /> <br /> <span class ="massage" > 邮箱:</span > <input class ="kuang" type ="email" required ="required" name ="mail" placeholder =" 可用于找回密码" v-model ="mail" /> </span > <br /> <br /> <span class ="massage" > 验证码:</span > <input class ="kuang vcode" type ="text" required ="required" name ="vcode" pattern ="[0-9]{7}" placeholder =" 点击右侧图片可切换" v-model ="verificode" /> <img v-bind:src ="vcodeImg" class ="vcodeimg" v-on:click ="changeVcode" /> <div class ="button_box" > <input type ="submit" v-bind:value ="submit_btn" /> <input type ="button" v-on:click ="changeFormStatus" v-bind:value ="change_btn" /> </div > </form > </div > <div v-show ="isAnimation" v-on:click ="isAnimation=!isAnimation" class ="myanimation" > <canvas id ="myanimation" width ="100px" height ="100px" > </canvas > </div > </div > </body >
js和css:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
<script src ="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" > </script > <script src ="https://cdn.staticfile.org/axios/0.18.0/axios.min.js" > </script > <script src ="http://passport.cnblogs.com/scripts/jsencrypt.min.js" > </script > ...(为了提高代码易读性,我将该部分单独放到后边的代码块中) <script > axios.defaults.baseURL = 'http://qsdbl.site:8080/myinterface' ; axios.jsonp = (url, data ) => { ...(为了提高代码易读性,我将该部分单独放到后边的代码块中) } var vue = new Vue({ el: '#app' , data: { isLogin: true , submit_btn: '登陆' , change_btn: '立即注册' , vcodeImg: '' , uuid: '' , timeout: 45, publicKey: '' , user: '' , passwd: '' , verificode: '' , mail: '' , isAnimation: false }, methods: { changeFormStatus: function () { this .isLogin = !this .isLogin; if (this .isLogin) { this .submit_btn = '登陆' ; this .change_btn = '立即注册' ; } else { this .submit_btn = '注册' ; this .change_btn = '有账号?点我登陆' ; } }, mysubmit: function () { ...(为了提高代码易读性,我将该部分单独放到后边的代码块中) }, formVali: function (event) { ...(为了提高代码易读性,我将该部分单独放到后边的代码块中) }, changeVcode: function () { axios.jsonp("/verificode" , { timeout: this .timeout, uuid: this .uuid }).then(function (res) { vue.uuid = res.uuid; vue.vcodeImg = axios.defaults.baseURL + res.vpath; }).catch(err => console .log("axios请求出错,err:" + err)) } }, mounted() { this .changeVcode(); window .setInterval(function ( ) { vue.changeVcode(); }, this .timeout * 1000 ); axios.jsonp("/getkey" ).then(function (res) { vue.publicKey = res.publicKey; }).catch(err => console .log("axios请求出错,err:" + err)) } }); </script > ...(为了提高代码易读性,我将该部分单独放到后边的代码块中)
封装的jsonp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
axios.jsonp = (url, data ) => { if (!url) { throw new Error ('url is necessary' ) } else { if (axios.defaults.baseURL != undefined ) { url = axios.defaults.baseURL + url; } } const callback = 'CALLBACK' + Math .random().toString().substr(9 , 18 ) const JSONP = document .createElement('script' ) JSONP.setAttribute('type' , 'text/javascript' ) const headEle = document .getElementsByTagName('head' )[0 ] let ret = '' ; if (data) { if (typeof data === 'string' ) ret = '&' + data; else if (typeof data === 'object' ) { for (let key in data) ret += '&' + key + '=' + encodeURIComponent (data[key]); } ret += '&_time=' + Date .now(); } JSONP.src = `${url} ?callback=${callback} ${ret} ` ; return new Promise ((resolve, reject ) => { window [callback] = r => { resolve(r) headEle.removeChild(JSONP) delete window [callback] } headEle.appendChild(JSONP) }) }
提交数据mysubmit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
mysubmit: function ( ) { if (this .isLogin) { vue.isAnimation = true ; let jsencrypt = new JSEncrypt(); jsencrypt.setPublicKey(vue.publicKey.trim()); axios.jsonp("/login" , { user: jsencrypt.encrypt(vue.user.trim()), passwd: jsencrypt.encrypt(vue.passwd.trim()), verificode: jsencrypt.encrypt(vue.verificode.trim()), uuid: jsencrypt.encrypt(vue.uuid.trim()) }).then(function (respon ) { console .log(respon); vue.isAnimation = false ; if (!respon.isVerificode) { alert("验证码输入错误!" ); } else { if (respon.status) { alert("登陆成功!" ); } else { switch (respon.type) { case "deprecated_new" : alert("请先激活账户再登陆,注意查收激活邮件📧" ); break ; case "deprecated" : alert("当前账户已注销无法使用!!!" ); break ; default : alert("密码有误!登陆失败!" ); break ; } } } }).catch(function ( ) { vue.isAnimation = false ; }) } else { vue.isAnimation = true ; console .log("向服务器发起 注册 请求。。。" ); let jsencrypt = new JSEncrypt(); jsencrypt.setPublicKey(vue.publicKey.trim()); axios.jsonp("/register" , { user: jsencrypt.encrypt(vue.user.trim()), passwd: jsencrypt.encrypt(vue.passwd.trim()), verificode: jsencrypt.encrypt(vue.verificode.trim()), uuid: jsencrypt.encrypt(vue.uuid.trim()), mail: jsencrypt.encrypt(vue.mail.trim()) }).then(function (respon ) { vue.isAnimation = false ; console .log(respon); if (!respon.isVerificode) { alert("验证码输入错误!" ); } else { if (respon.isExist) { alert("用户名" + respon.user + "已存在,请重新设置用户名!" ); } else { if (respon.status) { alert("账户注册成功!账户需要激活才能正常登陆,激活链接已发至邮箱" + vue.mail + ",请留意查收!" ); } else { alert("账户注册失败,请联系网站客服!" ) } } } }).catch(function ( ) { vue.isAnimation = false ; }) } }
自定义提示语
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
formVali: function (event ) { var elem = event.target; var vali = elem.validity; var name = elem.name; switch (name) { case "name" : if (vali.valueMissing) { elem.setCustomValidity("用户名不能为空!" ); } else { elem.setCustomValidity("" ); } break ; case "passwd" : if (vali.valueMissing) { elem.setCustomValidity("密码不能为空!" ); } else { elem.setCustomValidity("" ); } break ; case "mail" : if (vali.valueMissing) { elem.setCustomValidity("邮箱不能为空!" ); } else { elem.setCustomValidity("" ); } break ; case "vcode" : if (vali.valueMissing) { elem.setCustomValidity("请输入验证码!" ); } else { if (vali.patternMismatch) { elem.setCustomValidity("格式有误!" ); } elem.setCustomValidity("" ); } break ; } }
动画的js+css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
<!-- 动画的js、css --> <script type="text/javascript" > var pen = (document .querySelector('#myanimation' )).getContext('2d' ); pen.beginPath(); pen.lineWidth = 10 ; var color = pen.createLinearGradient(20 , 40 , 78 , 75 ); color.addColorStop(0 , '#212121' ); color.addColorStop(1 , '#ffffff' ); pen.strokeStyle = color; pen.arc(50 , 50 , 40 , (3 / 4 ) * Math .PI, 2.25 * Math .PI); pen.stroke(); </script> <style type="text/ css"> .myanimation { width: 100vw; height: 100vh; background-color: rgba(255, 255, 255, 0.8); position: fixed; top: 0; left: 0; z-index: 10; display: flex; justify-content: center; align-items: center; } .myanimation canvas { animation: xz 0.7s linear infinite; width: 50px; } @keyframes xz { from { transform: rotateZ(360deg); } to { transform: rotateZ(0deg); } } </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
<!-- form 表单的样式 -- > <style > * { margin : 0 ; padding : 0 ; } body { width : 100vw ; height : 100vh ; display : flex; align-items : center; justify-content : center; } .form_bigbox { width : auto; height : auto; margin : auto; } .form_title { border-bottom : 2px solid #d5d5d5 ; height : 40px ; } .form_title h2 { border-left : 4px solid #000000 ; padding-left : 5px ; } .form_bigbox form { margin-top : 30px ; } .form_bigbox form .massage { display : inline-block; width : 70px ; height : 20px ; line-height : 20px ; text-align : right; } .form_bigbox form .kuang { width : 250px ; margin-right : 5px ; height : 20px ; } .form_bigbox form .button_box { width : 100% ; text-align : center; } .form_bigbox form .button_box input { width : 130px ; height : 25px ; margin : 30px 10px ; font-size : 14px ; font-weight : bold; cursor : pointer; } .form_bigbox form .vcode { width : 130px ; margin-right : 10px ; } .form_bigbox form .vcodeimg { display : inline-block; width : 100px ; cursor : pointer; } .form_bigbox form input :valid { background-color : #d5d5d5 ; } </style>
后端接口
注册接口的大致工作流程如上图所示。我们需要编写两个Servlet程序,处理注册请求和激活账户操作。还需要对原数据库中的account表 进行修改,添加列mail。
处理注册请求
编写类Register,处理注册请求。web.xml中配置的请求路径为”/register”。
对CheckLogin类 的方法checkParameter、方法checkVcode进行小小的修改,这里可以直接调用,检查参数合法性和检查验证码是否正确。参数user、passwd、mail、verificode、uuid
都是必须的,否则直接退出程序。saveData 方法,保存用户数据到数据库(24小时内未进行激活操作,则删除账户),返回boolean值,true-当前用户名已存在即重名了。false-没有重名,账户已注册成功。sendMail 方法,发送邮件给用户,用于激活账户。单独写成一个类SendVerifyMail ,用于发送邮件,使用多线程优化用户体验(减少等待时间)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
package com.qsdbl.login;import com.alibaba.fastjson.JSONObject;import com.qsdbl.connect.Account;import com.qsdbl.connect.Table_account;import com.qsdbl.util.RSAutils;import com.qsdbl.util.SendVerifyMail;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.beans.PropertyVetoException;import java.io.IOException;import java.io.PrintWriter;import java.util.Objects;public class Register extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!CheckLogin.checkParameter(new String[]{"user" ,"passwd" ,"verificode" ,"uuid" ,"mail" },req)){ return ; } resp.setContentType("application/jsonp; charset=utf-8" ); String user = RSAutils.decryptBase64(req.getParameter("user" ).trim()); String passwd = RSAutils.decryptBase64(req.getParameter("passwd" ).trim()); String mail = RSAutils.decryptBase64(req.getParameter("mail" ).trim()); String verificode = RSAutils.decryptBase64(req.getParameter("verificode" ).trim()); String uuid = RSAutils.decryptBase64(req.getParameter("uuid" ).trim()); boolean status = false ; boolean isVerificode = false ; boolean isExist = true ; if (CheckLogin.checkVcode(verificode,uuid,req)){ isVerificode = true ; try { isExist = saveData(user,passwd,mail); } catch (Exception throwables) { throwables.printStackTrace(); } if (!isExist){ status = true ; sendMail(new Account(user, passwd, mail)); } } JSONObject json = new JSONObject(); json.put("user" , user); json.put("status" ,status); json.put("isExist" ,isExist); json.put("isVerificode" ,isVerificode); String funString = req.getParameter("callback" ); PrintWriter writer = resp.getWriter(); writer.write(funString + "(" +json.toString()+")" ); writer.flush(); writer.close(); } public boolean saveData (String user,String passwd,String mail) { ... } public void sendMail (Account account) { new SendVerifyMail(account).start(); } }
saveData
保存用户数据到数据库(24小时内未进行激活操作,则删除账户)。不是真正的删掉账户,而是将其账户类型更改为deprecated,代表已废弃已注销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
public boolean saveData (String user,String passwd,String mail) { boolean status = true ; final Account account = new Account(user, passwd, mail,"deprecated_new" ); try { Table_account table_account = new Table_account(); String isExist = table_account.find_name(account.getUser()); if (!isExist.equals("exist" )){ String res = table_account.add(account); if (res.equals("success" )){ status = false ; new Thread(){ @Override public void run () { try { sleep(1000 *60 *60 *24 ); Table_account table_account = new Table_account(); Account databaseAccount = table_account.findByName(account.getUser()); if (databaseAccount.getType().equals("deprecated_new" )){ String s = table_account.del(account.getUser()); } } catch (Exception e) { e.printStackTrace(); } } }.start(); } } } catch (Exception e) { e.printStackTrace(); } return status; }
SendVerifyMail
发送邮件给用户,用于激活账户。关于发送邮件,详细笔记可查看这里 。
激活账户,使用超链接get请求传递用户名和密码给服务器的方式。激活账户使用到Servlet程序ActivateAccount ,请求地址为“/activate”,见下边第67-72行。
邮件发送用到的maven依赖(整个案例中使用到的所有依赖,见博客最后边的源码):
1 2 3 4 5 6 7 8 9 10 11 12 13
<dependency > <groupId > javax.mail</groupId > <artifactId > mail</artifactId > <version > 1.5.0-b01</version > </dependency > <dependency > <groupId > javax.activation</groupId > <artifactId > activation</artifactId > <version > 1.1.1</version > </dependency >
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
package com.qsdbl.util;import com.qsdbl.connect.Account;import com.sun.mail.util.MailSSLSocketFactory;import javax.mail.*;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeMessage;import java.util.Properties;public class SendVerifyMail extends Thread { private String host = "smtp.163.com" ; private String from = "qsdbl_wy@163.com" ; private String username = "qsdbl_wy@163.com" ; private String authCode = "BZMIKFJCTFSTBPRQ" ; private Account account; public SendVerifyMail (Account account) { this .account = account; } @Override public void run () { try { Properties prop = new Properties(); prop.setProperty("mail.host" ,host); prop.setProperty("mail.transport.protocol" ,"smtp" ); prop.setProperty("mail.smtp.auth" ,"true" ); MailSSLSocketFactory sf = new MailSSLSocketFactory(); sf.setTrustAllHosts(true ); prop.put("mail.smtp.ssl.enable" ,"true" ); prop.put("mail.smtp.ssl.socketFactory" ,sf); Session session = Session.getDefaultInstance(prop, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication () { return new PasswordAuthentication(username,authCode); } }); Transport ts = session.getTransport(); ts.connect(host,username,authCode); MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO,new Address[]{new InternetAddress(account.getMail()),new InternetAddress(from)}); message.setSubject("注册成功®️" ); StringBuilder text = new StringBuilder(); text.append("<span style=\"display:block;width:100vw;height: 100vh;font-size: 1rem;\"><form><fieldset><legend style=\"color: red;\">激活账户</legend><span style=\"color: red;\">" ); text.append(account.getUser()); text.append("</span>,您好,感谢您的注册🎉,请务必阅读以下内容。" ); text.append("<br/>您的用户名:" ); text.append(account.getUser()); text.append("<br/>您的密码:" ); text.append(account.getPasswd()); text.append("<br/>请妥善保管,如有问题请联系网站客服!" ); text.append("<br/>点击👉<a href=\"" ); text.append("http://localhost:8080" ); text.append("/myinterface/activate?user=" ); text.append(account.getUser()); text.append("&passwd=" ); text.append(account.getPasswd()); text.append("\">这里</a>👈激活注册账户。请在24小时内激活您刚刚注册的账户否则账户将会被回收,若无法点击请用浏览器查看本邮件📧。如非本人操作,请忽略此邮件!<br/><img src='../../../images/markdown_img/2020/20200905111242.gif' style='width:200px'/></fieldset></form></span>" ); message.setContent(text.toString(),"text/html;charset=utf-8" ); ts.sendMessage(message, message.getAllRecipients()); ts.close(); } catch (Exception e) { e.printStackTrace(); } } }
由于注册账户模块中涉及到账户类型 的操作。故对类CheckLogin进行相应的修改,在验证账户的用户名与密码之前先检查用户的账户类型,如果不是普通账户或管理员账户则不进行检查。返回给前端的数据中多了个type 参数,可以根据type判断验证失败的原因是不是账户已经注销或者新注册未激活,若不是上边两种情况则是用户输入的用户名、密码不正确。
CheckLogin 的修改(在第54行):
1 2 3 4 5 6 7 8 9 10
Account databaseAccount = new Table_account().findByName(account.getUser()); type = databaseAccount.getType(); if (type.equals("admin" ) || type.equals("common" )){ status = new Table_account().check_passwd(account); } json.put("type" ,type);
激活账户
ActivateAccount ,激活账户,更改账户类型为普通用户-common。请求地址为“/activate”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
package com.qsdbl.util;import com.qsdbl.connect.Account;import com.qsdbl.connect.Table_account;import com.qsdbl.login.CheckLogin;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.beans.PropertyVetoException;import java.io.IOException;import java.io.PrintWriter;public class ActivateAccount extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!CheckLogin.checkParameter(new String[]{"user" ,"passwd" },req)){ return ; } String user = req.getParameter("user" ); String passwd = req.getParameter("passwd" ); if (user.length()>0 && passwd.length()>0 ){ try { Account account = new Account(); account.setUser(user); account.setPasswd(passwd); Account database_account = new Table_account().findByName(user); if (database_account.getUser().equals(user) && database_account.getPasswd().equals(passwd) && database_account.getType().equals("deprecated_new" )){ new Table_account().alterType(account); }else { resp.setContentType("text/html; charset=utf-8" ); PrintWriter writer = resp.getWriter(); writer.write("<span style=\"display:block;width:100vw;height: 100vh;line-height: 100vh; text-align: center;color: red;font-size: 1.55rem;\">该账户已过了激活期限!请联系网站客服或重新注册®️。</span>" ); writer.flush(); writer.close(); return ; } } catch (PropertyVetoException e) { e.printStackTrace(); } }else { return ; } resp.setContentType("text/html; charset=utf-8" ); PrintWriter writer = resp.getWriter(); writer.write("<span style=\"display:block;width:100vw;height: 100vh;line-height: 100vh; text-align: center;color: red;font-size: 1.55rem;\">账户已激活成功!可以正常使用了。</span>" ); writer.flush(); writer.close(); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<servlet > <servlet-name > activate</servlet-name > <servlet-class > com.qsdbl.util.ActivateAccount</servlet-class > </servlet > <servlet-mapping > <servlet-name > activate</servlet-name > <url-pattern > /activate</url-pattern > </servlet-mapping > <servlet > <servlet-name > register</servlet-name > <servlet-class > com.qsdbl.login.Register</servlet-class > </servlet > <servlet-mapping > <servlet-name > register</servlet-name > <url-pattern > /register</url-pattern > </servlet-mapping >
测试
1 2 3 4 5 6 7 8
- - 同域 - - 访问部署在Tomcat中的web页面(验证模块部署在Tomcat的myinterface中): http://qsdbl.site:8080/myinterface/LR.html https://qsdbl.site:8443/myinterface/LR.html - - 跨域 - - 访问部署在Apache中的web页面(验证模块部署在Tomcat的myinterface中): https://www.qsdbl.site/private/myinterface/LR.html
访问部署在Apache中的web页面:
登陆账户:
注册账户:
邮件激活账户:
如果过了激活期限(24小时),再点击链接,则显示的是:
源码
原案例,只有普通的用户名、密码验证功能(源码只有打包后的war包):
https://cloud.189.cn/t/YfMVF36vEZz2
(访问码:kk0g)
改进后,加了验证码功能和RSA加密,响应速度变慢了许多(源码有IDEA项目文件和war包):
https://cloud.189.cn/t/6Bfmm2jmIrEj
(访问码:vz4b)
注册+登陆,添加了注册模块:https://cloud.189.cn/t/7JRji2eIj2Q3
(访问码:x3lv)