Java web 登录加验证码案例
2020/8/25
在上次的登录案例中加入验证码的处理和使用jsp进行操作
需求
- 访问带有验证码的登录页面login.jsp
- 用户输入用户名,密码以及验证码。
- 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
- 如果验证码输入有误,跳转登录页面,提示:验证码错误
- 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
1.绘制验证码(servlet)
在上次的验证码的基础上对验证码进行了细节上的操作,使背景填充随机颜色,字符的高度随机位置,干扰线的随机颜色
代码如下
package zsc.Servlet.Check;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
/***
* 验证码的servlet
*/
@WebServlet("/CheckDemo1")
public class CheckDemo1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取随机颜色的内部类
class color{
public Color getcolor(){
Random ra=new Random();
int r = ra.nextInt(256);
int g = ra.nextInt(256);
int b = ra.nextInt(256);
return new Color(r,g,b);
}
}
//1.创建一个内存中的图片对象
int width=100;
int height=50;
BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//2.绘画图片(要先填充颜色再画边框,这样边框才会显示)
//1.获取画笔对象
Graphics g=image.getGraphics();
//2.填充随机颜色(使用color内部类的getcolor方法)
g.setColor(new color().getcolor());
g.fillRect(0,0,width,height);
//3.画边框
g.setColor(Color.BLUE);
g.drawRect(0,0,width-1,height-1);
//4.画入验证码
String str="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789";
Random ran=new Random();
String ss=""; //该字符串用于存储验证码的值
for (int i = 0; i < 4; i++) {
//取随机字符
int index = ran.nextInt(str.length());
char c = str.charAt(index);
//取随机画字符的高度范围为20-40,int s = random.nextInt(max)%(max-min+1) + min;
int h = ran.nextInt(40)%(40-20+1)+20;
//设置画笔画字符的字体大小和画笔颜色
Font font=new Font("宋体", Font.ITALIC,30);
g.setFont(font);
g.setColor(Color.white);
g.drawString(c+"",width/6*(i+1),h);
ss=ss+c;
}
System.out.println(ss);
//5.将验证码的值存入会话作用域
HttpSession session = request.getSession();
session.setAttribute("check",ss);
//6.画随机的干扰线
for (int i = 0; i < 10; i++) {
//设置画笔颜色(随机色)
g.setColor(new color().getcolor());
//2.设置干扰线的宽度(使用Graphics2D类的setStroke方法)
Graphics2D g2d= (Graphics2D) g;
g2d.setStroke(new BasicStroke(1.5f));
//取随机的干扰线的两点
int x1 = ran.nextInt(width);
int y1 = ran.nextInt(height);
int x2 = ran.nextInt(width);
int y2 = ran.nextInt(height);
g.drawLine(x1,y1,x2,y2);
}
//3.将图片发送响应
ImageIO.write(image,"jpg",response.getOutputStream());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
这里使用了一个能产生随机颜色的内部类,里面有一个getcolor的方法来生成随机颜色
class color{
public Color getcolor(){
Random ra=new Random();
int r = ra.nextInt(256);
int g = ra.nextInt(256);
int b = ra.nextInt(256);
return new Color(r,g,b);
}
}
在将字符串绘制到图像上后要将字符串存储到session会话作用域中共享数据。
2.数据库操作的操作类
和上次的登录案例一样使用一样的方法使用druid数据库连接池和JDBCtemplate来操作数据库
1.封装数据库的数据的JavaBean对象
代码如下
package zsc.Servlet.JavaB;
public class Students {
Integer id;
String name;
String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Students{" +
"id=" + id +
", name='" + name + '\'' +
", possword='" + password + '\'' +
'}';
}
}
2.获取数据库连接池的工具类
代码如下
package zsc.Servlet.JDBC;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.naming.Context;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static DataSource ds;
static {
//使用Druid数据连接池连接数据库
try {
Properties properties=new Properties();
properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接池方法
public static DataSource getdataSource(){
return ds;
}
//获取连接方法
public static Connection getconnection() throws SQLException {
return ds.getConnection();
}
//关闭连接的方法
public static void close(PreparedStatement ps, ResultSet rs,Connection con){
if (ps!=null){
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (rs!=null){
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (con!=null){
try {
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
3.操作数据库的登录类
里面有一个登录的方法来查找该用户是否存在,如果不存在就返回-1,如果存在密码错误返回0,密码正确返回1
里面还有一个静态的Students的JavaBean对象来获取查询完后的Students对象
代码如下
package zsc.Servlet.DoMain;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import zsc.Servlet.JDBC.JDBCUtils;
import zsc.Servlet.JavaB.Students;
import java.util.Map;
public class Login {
//1.实现JdbcTemplate对象,并且接收获取数据库连接池,该对象是私有的成员对象
private JdbcTemplate jdbcTemplate=new JdbcTemplate(JDBCUtils.getdataSource());
//创建一个Students对象,方便servlet获取查询后的Students对象
public static Students stu=null;
public int getLogin(Students student){
try {
//2.创建数据库查询语句
// (查找该用户是否存在,如果不存在就返回-1,如果存在密码错误返回0,密码正确返回1)
String sql="select * from studentTable1 where name=?";
//3.使用queryForObject方法来操作查询语句
stu = jdbcTemplate.queryForObject(sql,
new BeanPropertyRowMapper<Students>(Students.class),
student.getName());
//4.如果报异常,那证明查找不到返回-1,对查找到的用户进行,密码判断
if (stu.getPassword().equals(student.getPassword())){
return 1;
//用户名和密码都正确返回1
}
else {
return 0;
//密码错误返回0
}
} catch (DataAccessException e) {
e.printStackTrace();
return -1;
//用户名错误或者没有该用户返回-1
}
}
}
3.使用工具类的servlet
该servlet使用工具类来操作数据库,接收返回值,和调用登录类里的Students(JavaBean)对象,然后进行登录情况的处理,请求转发和响应重定向
三次错误的处理是使用请求转发来跳转页面,都是跳转登录页面,而正确的处理使用响应重定向来跳转页面,跳转到success的jsp页面
package zsc.Servlet.Main;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import zsc.Servlet.DoMain.Login;
import zsc.Servlet.JavaB.Students;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
@WebServlet("/LoginServle")
public class LoginServle extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决中文乱码
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//1.BeanUtils的populate静态方法来获取所有的请求参数,并且直接写进Students的javaBean对象中
//创建javaBean对象,Students对象
Students student=new Students();
//将获取的所有请求参数封装到map集合中
Map<String, String[]> parameterMap = request.getParameterMap();
try {
//使用populate方法
BeanUtils.populate(student,parameterMap);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//2.判断验证码是否正确(如果正确进行登录的操作,如果不正确开始提示用户)
//三次错误的处理是使用请求转发来跳转页面,而正确的处理使用响应重定向来跳转页面
//获取页面输入的验证码
String check = request.getParameter("check");
//获取session对象,并且获取图像的验证码
HttpSession session = request.getSession();
String session_check = (String) session.getAttribute("check");
//获取完session对象的的验证码,然后立马删除session里的验证码,使验证码为一次性的
session.removeAttribute("check");
if (session_check!=null&&session_check.equalsIgnoreCase(check)){ //防止出现空指针异常,所以先判断一下是否为空忽略大小写的比较方法.equalsIgnoreCase
//验证码一致时
//获取数据库操作对象
Login login=new Login();
//使用.getLogin方法
int loginInt = login.getLogin(student);
//获取查询完数据库的students JavaBean对象
Students stu = Login.stu;
if (loginInt==-1){
//请求转发
request.setAttribute("student_wrong","没有该用户,或者用户名错误!");
request.getRequestDispatcher("/index.jsp").forward(request,response);
}
else if (loginInt==0){
//请求转发
request.setAttribute("password_wrong","密码错误");
request.getRequestDispatcher("/index.jsp").forward(request,response);
}
else {
//响应重定向
session.setAttribute("success_name",stu.getName());
session.setAttribute("success_password",stu.getPassword());
response.sendRedirect(request.getContextPath()+"/success.jsp");
}
}
else {
//验证码不一致时,使用请求转发,并且使用请求作用域存储错误信息
//存储错误信息
request.setAttribute("check_wrong","验证码错误");
//转发到登录页面
request.getRequestDispatcher("index.jsp").forward(request,response);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
注意在获取完验证码的共享数据后一定要删除验证码的共享数据,然后在判断的时候要进行一次判空操作(防止出现空指针异常),这样就会使验证码是一次性的,在正确登录后点击浏览器上的返回,就不会出现验证码复用的问题
4.两个jsp页面
一个登录的主页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
<style>
*{
margin: 0px;
padding: 0px;
}
div{
/*border: red solid 1px;*/
}
/*解决图片验证码单击的事件处理*/
</style>
<script type="text/javascript">
window.onload=function () {
document.getElementById("img1").onclick=function () {
var date=new Date().getTime();
this.src="/zsc/CheckDemo1?"+date;
}
document.getElementById("a1").onclick=function () {
var date=new Date().getTime();
var img=document.getElementById("img1");
img.src="/zsc/CheckDemo1?"+date;
}
}
</script>
</head>
<body>
<div style="background: aqua;width: 100%;height: 100%;position: absolute">
<div style="position: absolute;left: 650px;top: 250px;">
<form action="/zsc/LoginServle" method="post">
用户名:<input type="text" name="name" placeholder="请输入用户名"><br><br>
密 码:<input type="password" name="password" placeholder="请输入密码"><br><br>
验证码:<input type="text" name="check" placeholder="请输入验证码"><br>
<div style="position: absolute;left: 25px;top: 200px;">
<input type="submit" value="登录" style="margin-left: 20px">
<input type="reset" value="重置" style="margin-left: 100px">
</div>
<img src="/zsc/CheckDemo1" style="margin-top: 20px" id="img1">
<a href="#" id="a1" style="text-decoration: none">看不清,换一张</a>
<%--style="text-decoration: none"去超链接的下划线,超链接页面不刷新,设置href="#"--%>
<div style="position: absolute;width: 250px;height: 20px;left: 250px;top: 10px;color: red">
<%
if (request.getAttribute("student_wrong")!=null){
out.println(request.getAttribute("student_wrong"));
}
%>
</div>
<div style="position: absolute;width: 250px;height: 20px;left: 250px;top: 50px;color: red">
<%
if (request.getAttribute("password_wrong")!=null){
out.println(request.getAttribute("password_wrong"));
}
%>
</div>
<div style="position: absolute;width: 250px;height: 20px;left: 250px;top: 90px;color: red">
<%
if (request.getAttribute("check_wrong")!=null){
out.println(request.getAttribute("check_wrong"));
}
%>
</div>
</form>
</div>
</div>
</form>
</body>
</html>
一个登录成功重定向的页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登入成功</title>
</head>
<body>
<%
if (request.getSession().getAttribute("success_name")!=null){
out.println(request.getSession().getAttribute("success_name"));
out.println("<a href='/zsc/index.jsp'>注销登录</a>");
}
else {
out.println("抱歉你未登录,请返回登录页面");
out.println("<a href='/zsc/index.jsp'>登录</a>");
}
%>
</body>
</html>
里面使用java代码来对登录情况的提示语句进行处理