Web服务器端应用开发

一、登录验证器

1.1相关概念

登录验证器是一种用于提高帐户安全性的应用或设备,它可以在你输入用户名和密码后,生成或接收一个一次性的验证码或通知,以进行第二次身份验证。这样,即使你的密码被泄露或破解,其他人也无法轻易登录你的帐户,除非他们也拥有你的登录验证器。登录验证器也称为双重验证、两步验证或多因素验证。

有许多类型的登录验证器,例如:

  • 基于时间的一次性密码 (TOTP) 应用,如 Microsoft Authenticator1、Google Authenticator、Authy 等,它们可以在你的手机上生成一个每隔几秒就变化的六位数验证码,你需要在登录时输入该验证码。
  • 推送通知应用,如 Microsoft Authenticator1、Duo Mobile 等,它们可以在你的手机上接收一个来自登录网站的通知,你只需点击批准或拒绝按钮来完成登录。
  • 硬件令牌设备,如 YubiKey、RSA SecurID 等,它们是一种类似于 U 盘的物理设备,可以插入你的电脑或手机上的 USB 端口或 NFC 读取器,以产生一个唯一的验证码或密钥来验证你的身份。
  • 短信 (SMS) 或电话呼叫服务,如 Google Voice、Twilio 等,它们可以向你注册的手机号码发送一个验证码短信或拨打一个电话来确认你的登录。

1.2前端代码

1.2.1登录界面

 

<html>
<head><meta charset="UTF-8"><title>登录验证</title><style>body {background-color: lightblue;font-family: Arial, sans-serif;}h1 {text-align: center;color: white;}form {width: 300px;margin: 0 auto;padding: 20px;border: 2px solid white;border-radius: 10px;}label {display: inline-block;width: 20%;text-align: right;}input {display: inline-block;width: 60%;margin: 10px;}button {display: inline-block;width: 40%;margin: 10px;padding: 5px;border: none;border-radius: 5px;color: white;background-color: darkblue;cursor: pointer;}a {color: white;text-decoration: none;}</style>
</head>
<body>
<h1>欢迎登录</h1>
<form action="validate" name="test"method="get"><div><label for="uname">用户:</label><input type="text" name="uname" id="uname" placeholder="请输入您的电子邮件"><br><label for="upass">密码:</label><input type="password" name="upass" id="upass" placeholder="请输入您的密码"><br><button type="submit">登录</button><button type="reset">清空</button></div><br><div style="text-align: center;"><a href="newuser.html">注册新用户 </a></div>
</form>
</body>
</html>

1.2.2登录失败界面

<html>
<head><meta charset="UTF-8"><title>登录验证</title><style>body {background-color: lightblue;font-family: Arial, sans-serif;}h1 {text-align: center;color: white;}p {text-align: center;font-size: 20px;color: red;}a {color: white;text-decoration: none;}</style>
</head>
<body>
<h1>登录失败</h1>
<p>抱歉,您输入的用户名或密码不正确,请重新输入或注册新用户。</p>
<div style="text-align: center;"><a href="index.html">返回登录页面 </a><a href="newuser.html">注册新用户 </a>
</div>
</body>
</html>

1.2.3注册界面

<html>
<head><meta charset="UTF-8"><title>登录验证</title><style>body {background-color: lightblue;font-family: Arial, sans-serif;}h1 {text-align: center;color: white;}form {width:300px;margin: 0 auto;padding: 20px;border: 2px solid white;border-radius: 10px;}label {display: inline-block;width: 20%;text-align: right;}input {display: inline-block;width: 60%;margin: 10px;}button {display: inline-block;width: 40%;margin: 10px;padding: 5px;border: none;border-radius: 5px;color: white;background-color: darkblue;cursor: pointer;}a {color: white;text-decoration: none;}</style>
</head>
<body>
<h1>欢迎注册</h1>
<form action="newuser" name="test"method="get"><div><label for="uname">用户:</label><input type="text" name="uname" id="uname" placeholder="请输入您的电子邮件"><br><label for="upass">密码:</label><input type="password" name="upass" id="upass" placeholder="请输入您的密码"><br><button type="submit">注册</button><button type="reset">清空</button></div></form>
</body>
</html>

1.2.4注册失败界面

<html>
<head><meta charset="UTF-8"><title>注册验证</title><style>body {background-color: lightblue;font-family: Arial, sans-serif;}h1 {text-align: center;color: white;}p {text-align: center;font-size: 20px;color: red;}a {color: white;text-decoration: none;}</style>
</head>
<body>
<h1>注册失败</h1>
<p>抱歉,您输入的用户名或电子邮件已经被占用,请重新输入或登录已有账户。</p>
<div style="text-align: center;"><a href="newuser.html">返回注册页面 </a><a href="login.html">进入登录页面 </a>
</div>
</body>
</html>

 1.2.5注册成功界面

<html>
<head><meta charset="UTF-8"><title>注册验证</title><style>body {background-color: lightblue;font-family: Arial, sans-serif;}h1 {text-align: center;color: white;}p {text-align: center;font-size: 20px;color: green;}a {color: white;text-decoration: none;}</style>
</head>
<body>
<h1>注册成功</h1>
<p>恭喜您,您已经成功注册您的账户</p>
<div style="text-align: center;"><a href="profile.html">进入个人中心 </a><a href="index.html">返回登录页面 </a>
</div>
</body>
</html>

1.3后端代码

实现一个简单的用户管理系统,基本的Java Web应用程序,用于进行用户注册和登录验证的操作。其中,DBAccess 类处理数据库操作,Newuser 类负责用户注册,Validate 类负责登录验证。

1.3.1数据库代码

package org.example;
import java.sql.*;// 数据库操作类,用于与数据库进行交互
public class DBAccess {private String driver = "com.mysql.jdbc.Driver"; // 数据库驱动private String url = "jdbc:mysql://localhost:3306/test"; // 数据库连接URLprivate String user = "root"; // 数据库用户名private String password = "b123456"; // 数据库密码private Connection conn = null; // 数据库连接private Statement stmt = null; // 用于执行SQL语句的Statement对象// 初始化数据库连接public void init() {try {Class.forName(driver); // 加载并注册数据库驱动程序conn = DriverManager.getConnection(url, user, password); // 建立数据库连接stmt = conn.createStatement(); // 创建Statement对象} catch (ClassNotFoundException e) {System.out.println("找不到驱动程序");e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}}// 向数据库插入用户信息public void insert(String uname, String upass) throws SQLException {String str = "insert into users values('" + uname + "','" + upass + "')";stmt.execute(str);}// 更新数据库中的用户密码public void update(String uname, String upass) throws SQLException {String str = "update users set upass='" + upass + "' where uname='" + uname + "'";stmt.execute(str);}// 查询数据库中指定用户名的密码public String query1(String uname) throws SQLException {String str = "select upass from users where uname='" + uname + "'";ResultSet rs = stmt.executeQuery(str);rs.next();String result = rs.getString("upass");return result;}// 查询数据库中是否存在指定用户名public String query2(String uname) throws SQLException {String str = "select uname from users where uname='" + uname + "'";ResultSet rs = stmt.executeQuery(str);rs.next();String result = rs.getString("uname");return result;}// 提交并关闭数据库连接public void submit() throws SQLException {stmt.close();conn.close();}
}

1.3.2注册代码

package org.example;import java.sql.*;
import java.io.*;import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;@WebServlet("/newuser")
public class Newuser extends HttpServlet {String uname = null; // 待注册的用户名String upass = null; // 待注册的密码DBAccess dba; // 数据库访问对象// 初始化方法,在Servlet启动时调用public void init() {dba = new DBAccess(); // 创建数据库访问对象dba.init(); // 初始化数据库连接}// 处理HTTP的GET请求public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String tempuname = request.getParameter("uname"); // 从请求参数中获取用户名upass = request.getParameter("upass"); // 从请求参数中获取密码try {uname = dba.query2(tempuname).trim(); // 查询数据库,检查是否已存在相同用户名} catch (SQLException e) {System.out.println(e.getMessage());}if (uname != null) { // 如果用户名已存在response.sendRedirect("newusererror.html"); // 重定向到注册失败页面uname = null;} else {try {dba.insert(tempuname, upass); // 插入新用户信息到数据库response.sendRedirect("newuserok.html"); // 重定向到注册成功页面} catch (IOException e) {throw new RuntimeException(e);} catch (SQLException e) {throw new RuntimeException(e);}}}// 处理HTTP的POST请求,委托给doGet方法处理public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}
}

trim() 是一个常见的字符串处理函数,被用来去除字符串两端的空格。

1.3.3登录验证代码

package org.example;import java.sql.*;
import java.io.*;import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;@WebServlet("/validate")
public class Validate extends HttpServlet {String uname = null; // 待验证的用户名String upass = null; // 待验证的密码DBAccess dba; // 数据库访问对象// 初始化方法,在Servlet启动时调用public void init() {dba = new DBAccess(); // 创建数据库访问对象dba.init(); // 初始化数据库连接}// 处理HTTP的GET请求public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {uname = request.getParameter("uname"); // 从请求参数中获取用户名String temppass = request.getParameter("upass"); // 从请求参数中获取密码try {upass = dba.query1(uname).trim(); // 查询数据库,获取指定用户名的密码,并去除两端的空格} catch (SQLException e) {System.out.println(e.getMessage());}if (!temppass.equals(upass)) { // 如果输入的密码与数据库中的密码不匹配response.sendRedirect("error.html"); // 重定向到登录错误页面} else {response.sendRedirect("index.html"); // 重定向到登录成功页面}}// 处理HTTP的POST请求,委托给doGet方法处理public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}
}

1.4密码加密和验证

在注册代码中,您应该使用密码哈希来存储密码,而不是直接将原始密码存储在数据库中。在验证代码中,您需要将用户输入的密码通过相同的哈希算法进行加密,然后与数据库中存储的哈希密码进行比较。这样,即使数据库泄露,攻击者也无法得到原始密码。

1.4.1加密分类

在用户认证系统中,对密码进行加密是非常重要的,以增加安全性。常见的做法是使用哈希函数来对密码进行加密存储,这样即使数据库泄漏,攻击者也难以还原出用户的明文密码。以下是一种简单的做法:

1.密码哈希化:
在用户注册时,将用户的密码进行哈希化,并将哈希值存储在数据库中。不要直接将明文密码存储在数据库中。

2.使用Salt(盐值)增加安全性:
在密码哈希化过程中,使用一个随机的盐值。盐值是一个随机的字符串,与用户密码合并后再进行哈希。这会增加密码的随机性,提高安全性。

3.延迟哈希:
为了防止暴力破解攻击,可以对哈希函数进行多次迭代。这会增加攻击者破解密码所需的时间。 

1.4.2xml配置

<!--        密码加密库--><dependency><groupId>org.mindrot</groupId><artifactId>jbcrypt</artifactId><version>0.4</version> <!-- BCrypt库的版本号 --></dependency>

1.4.3加密代码

package org.example;import org.mindrot.jbcrypt.BCrypt;public class PasswordHashing {// 生成密码哈希public static String hashPassword(String password) {String salt = BCrypt.gensalt(12); // 生成盐值,12是工作因子(时间复杂度的对数)return BCrypt.hashpw(password, salt); // 使用盐值对密码进行哈希}// 验证密码public static boolean verifyPassword(String password, String hashedPassword) {return BCrypt.checkpw(password, hashedPassword); // 检查密码与哈希后的密码是否匹配}public static void main(String[] args) {String password = "user_password";// 生成密码哈希String hashedPassword = hashPassword(password);System.out.println("Hashed Password: " + hashedPassword);// 验证密码boolean isMatch = verifyPassword("user_password", hashedPassword);System.out.println("Password Match: " + isMatch);}
}

1.4.4改进的后端代码

数据库代码

import java.sql.*;/*** 这个类用于数据库访问操作,包括插入、更新和查询用户信息。*/
public class DBAccess {private String driver = "com.mysql.jdbc.Driver"; // 数据库驱动程序private String url = "jdbc:mysql://localhost:3306/test"; // 数据库连接URLprivate String user = "root"; // 数据库用户名private String password = "b123456"; // 数据库密码private Connection conn = null;private PreparedStatement insertStatement = null;private PreparedStatement updateStatement = null;private PreparedStatement queryPasswordStatement = null;private PreparedStatement queryUsernameStatement = null;/*** 初始化数据库连接和预编译语句。*/public void init() {try {Class.forName(driver); // 加载并注册驱动程序conn = DriverManager.getConnection(url, user, password); // 建立连接// 准备预编译语句insertStatement = conn.prepareStatement("INSERT INTO users (uname, upass) VALUES (?, ?)");updateStatement = conn.prepareStatement("UPDATE users SET upass = ? WHERE uname = ?");queryPasswordStatement = conn.prepareStatement("SELECT upass FROM users WHERE uname = ?");queryUsernameStatement = conn.prepareStatement("SELECT uname FROM users WHERE uname = ?");} catch (ClassNotFoundException e) {System.out.println("找不到驱动程序");e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}}/*** 将用户名和哈希后的密码插入到数据库中。** @param uname  用户名* @param upass  哈希后的密码*/public void insert(String uname, String upass) throws SQLException {insertStatement.setString(1, uname);insertStatement.setString(2, upass);insertStatement.executeUpdate(); // 执行插入操作}/*** 更新数据库中指定用户名的密码。** @param uname  用户名* @param upass  新的哈希后的密码*/public void update(String uname, String upass) throws SQLException {updateStatement.setString(1, upass);updateStatement.setString(2, uname);updateStatement.executeUpdate(); // 执行更新操作}/*** 查询指定用户名的密码。** @param uname  用户名* @return 查询到的哈希后的密码*/public String queryPassword(String uname) throws SQLException {queryPasswordStatement.setString(1, uname);ResultSet rs = queryPasswordStatement.executeQuery(); // 执行查询操作if (rs.next()) {return rs.getString("upass"); // 获取查询结果中的密码}return null;}/*** 查询数据库中是否存在指定用户名。** @param uname  用户名* @return 如果用户名存在返回true,否则返回false*/public boolean queryUsernameExists(String uname) throws SQLException {queryUsernameStatement.setString(1, uname);ResultSet rs = queryUsernameStatement.executeQuery(); // 执行查询操作return rs.next(); // 如果结果集不为空,说明用户名存在}/*** 关闭数据库连接和预编译语句。*/public void submit() throws SQLException {if (insertStatement != null) insertStatement.close();if (updateStatement != null) updateStatement.close();if (queryPasswordStatement != null) queryPasswordStatement.close();if (queryUsernameStatement != null) queryUsernameStatement.close();if (conn != null) conn.close();}
}

使用PreparedStatement:
当您构建SQL查询和更新时,最好使用PreparedStatement而不是直接拼接字符串。这可以防止SQL注入攻击。

注册代码

import java.io.*;
import java.sql.SQLException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;@WebServlet("/newuser")
public class Newuser extends HttpServlet {String uname = null;String upass = null;DBAccess dba;/*** 初始化方法,在Servlet启动时调用。*/public void init() {dba = new DBAccess();dba.init(); // 初始化数据库连接和预编译语句}/*** 处理GET请求。* 当用户访问注册页面时,通过GET请求触发此方法。*/public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String tempuname = request.getParameter("uname"); // 获取提交的用户名upass = request.getParameter("upass"); // 获取提交的密码try {if (dba.queryUsernameExists(tempuname)) { // 查询用户名是否已存在response.sendRedirect("newusererror.html"); // 如果用户名已存在,跳转到错误页面} else {// 对密码进行哈希操作后再存储String hashedPassword = PasswordHashing.hashPassword(upass); // 哈希化密码dba.insert(tempuname, hashedPassword); // 将用户名和哈希后的密码插入数据库response.sendRedirect("newuserok.html"); // 注册成功后跳转到成功页面}} catch (IOException | SQLException e) {throw new RuntimeException(e);}}/*** 处理POST请求。* 与GET请求处理相同,用于处理从注册页面提交的数据。*/public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response); // 调用与GET请求处理相同的方法}
}

登录验证代码

import java.io.*;
import java.sql.SQLException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;@WebServlet("/validate")
public class Validate extends HttpServlet {String uname = null;String upass = null;DBAccess dba;/*** 初始化方法,在Servlet启动时调用。*/public void init() {dba = new DBAccess();dba.init(); // 初始化数据库连接和预编译语句}/*** 处理GET请求。* 当用户尝试登录时,通过GET请求触发此方法。*/public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {uname = request.getParameter("uname"); // 获取提交的用户名String temppass = request.getParameter("upass"); // 获取提交的密码try {String hashedPassword = dba.queryPassword(uname); // 查询数据库中的哈希密码if (hashedPassword != null && PasswordHashing.verifyPassword(temppass, hashedPassword)) {// 验证哈希后的密码与数据库中的是否匹配response.sendRedirect("index.html"); // 密码正确,跳转到主页} else {response.sendRedirect("error.html"); // 密码错误,跳转到错误页面}} catch (SQLException e) {throw new RuntimeException(e);}}/*** 处理POST请求。* 与GET请求处理相同,用于处理从登录页面提交的数据。*/public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response); // 调用与GET请求处理相同的方法}
}

1.5基于连接池的数据库代码

1.5.1配置

   <!-- https://mvnrepository.com/artifact/com.mchange/c3pθ --><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.2</version></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency>

1.5.2连接类

package org.example;import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;public class C3p0Factory2 {private static ComboPooledDataSource dataSource = null;public static void init() throws Exception {// 创建 ComboPooledDataSource 实例,会自动加载 c3p0-config.xml 文件的配置dataSource = new ComboPooledDataSource();// 此时 dataSource 是一个完全配置好的可用连接池 DataSource}public static Connection getConnection() throws Exception {if (null == dataSource) {init();}// 返回从连接池获取的数据库连接return dataSource.getConnection();}
}

1.5.3数据库改进

package org.example;import java.sql.*;public class DBAccess {private Connection conn = null;private PreparedStatement insertStatement = null;private PreparedStatement updateStatement = null;private PreparedStatement queryPasswordStatement = null;private PreparedStatement queryUsernameStatement = null;public void init() {try {conn =C3p0Factory2.getConnection();// 准备语句insertStatement = conn.prepareStatement("INSERT INTO users (uname, upass) VALUES (?, ?)");updateStatement = conn.prepareStatement("UPDATE users SET upass = ? WHERE uname = ?");queryPasswordStatement = conn.prepareStatement("SELECT upass FROM users WHERE uname = ?");queryUsernameStatement = conn.prepareStatement("SELECT uname FROM users WHERE uname = ?");} catch (ClassNotFoundException e) {System.out.println("找不到驱动程序");e.printStackTrace();} catch (SQLException e) {e.printStackTrace();} catch (Exception e) {throw new RuntimeException(e);}}public void insert(String uname, String upass) throws SQLException {insertStatement.setString(1, uname);insertStatement.setString(2, upass);insertStatement.executeUpdate();}public void update(String uname, String upass) throws SQLException {updateStatement.setString(1, upass);updateStatement.setString(2, uname);updateStatement.executeUpdate();}public String queryPassword(String uname) throws SQLException {queryPasswordStatement.setString(1, uname);ResultSet rs = queryPasswordStatement.executeQuery();if (rs.next()) {return rs.getString("upass");}return null;}public boolean queryUsernameExists(String uname) throws SQLException {queryUsernameStatement.setString(1, uname);ResultSet rs = queryUsernameStatement.executeQuery();return rs.next();}public void submit() throws SQLException {if (insertStatement != null) insertStatement.close();if (updateStatement != null) updateStatement.close();if (queryPasswordStatement != null) queryPasswordStatement.close();if (queryUsernameStatement != null) queryUsernameStatement.close();if (conn != null) conn.close();}
}

1.6登录失败限制代码

1.6.1相关概念

  • 实施登录失败次数记录: 在数据库中为每个用户添加一个用于记录登录失败次数的字段。每次登录失败时,将该字段的值加一。

  • 实施登录失败时间记录: 在数据库中为每个用户添加一个用于记录最后登录失败的时间戳的字段。每次登录失败时,更新该字段为当前时间。

  • 设置登录失败阈值: 定义一个阈值,例如5次,表示用户在一定时间内最多可以尝试登录5次失败。

  • 设置登录失败时间限制: 定义一个时间窗口,例如15分钟,表示在该时间窗口内登录失败次数不得超过阈值。
  • 在登录验证器中实现限制逻辑: 在登录验证的代码中,对用户的登录失败次数和时间进行检查。如果登录失败次数超过阈值,并且当前时间与最后登录失败时间之间的时间差在时间窗口内,就禁止用户登录。

1.6.2添加前端代码

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>登录验证</title><style>body {background-color: lightblue;font-family: Arial, sans-serif;}h1 {text-align: center;color: white;}p {text-align: center;font-size: 20px;color: red;}a {color: white;text-decoration: none;}</style><script>// 获取服务器传递的剩余时间var remainingTime = <%= request.getAttribute("remainingTime") %>;function updateCountdown() {var minutes = Math.floor(remainingTime / 60000); // 将剩余时间转换为分钟var seconds = Math.floor((remainingTime % 60000) / 1000); // 将剩余时间转换为秒document.getElementById("countdown").innerText = minutes + " 分钟 " + seconds + " 秒"; // 更新倒计时文本remainingTime -= 1000; // 每次减去1秒if (remainingTime < 0) {document.getElementById("countdown").innerText = "已解除限制"; // 倒计时结束时显示已解除限制document.getElementById("retryLink").style.display = "inline"; // 显示重新尝试链接} else {setTimeout(updateCountdown, 1000); // 每秒更新倒计时}}window.onload = function() {updateCountdown(); // 初始化页面上的倒计时}</script>
</head>
<body>
<h1>登录失败</h1>
<p>抱歉,您已被阻止登录。您的账户已在一定时间内多次尝试登录失败。</p>
<p>限制时间还剩:<span id="countdown">计算中...</span></p> <!-- 显示剩余倒计时 -->
<div style="text-align: center;"><a href="index.html" id="retryLink" style="display: none;">返回登录页面 </a> <!-- 显示重新尝试链接 --><a href="newuser.html">注册新用户 </a>
</div>
</body>
</html>

1.6.3修改后的代码

数据库

package org.example;import java.sql.*;public class DBAccess {private Connection conn = null;private PreparedStatement insertStatement = null;private PreparedStatement updateStatement = null;private PreparedStatement queryPasswordStatement = null;private PreparedStatement queryUsernameStatement = null;private PreparedStatement queryFailedAttemptsStatement = null;private PreparedStatement queryLastFailedTimeStatement = null;private PreparedStatement clearFailedAttemptsStatement = null;private PreparedStatement recordFailedLoginAttemptStatement = null;public void init() {try {conn =C3p0Factory2.getConnection();// 准备语句insertStatement = conn.prepareStatement("INSERT INTO users (uname, upass) VALUES (?, ?)");updateStatement = conn.prepareStatement("UPDATE users SET upass = ? WHERE uname = ?");queryPasswordStatement = conn.prepareStatement("SELECT upass FROM users WHERE uname = ?");queryUsernameStatement = conn.prepareStatement("SELECT uname FROM users WHERE uname = ?");queryFailedAttemptsStatement = conn.prepareStatement("SELECT failed_attempts FROM users WHERE uname = ?");queryLastFailedTimeStatement = conn.prepareStatement("SELECT last_failed_time FROM users WHERE uname = ?");clearFailedAttemptsStatement = conn.prepareStatement("UPDATE users SET failed_attempts = 0, last_failed_time = NULL WHERE uname = ?");recordFailedLoginAttemptStatement = conn.prepareStatement("UPDATE users SET failed_attempts = ?, last_failed_time = ? WHERE uname = ?");} catch (ClassNotFoundException e) {System.out.println("找不到驱动程序");e.printStackTrace();} catch (SQLException e) {e.printStackTrace();} catch (Exception e) {throw new RuntimeException(e);}}/*** 插入新用户的用户名和加密后的密码到数据库。*/public void insert(String uname, String upass) throws SQLException {insertStatement.setString(1, uname);insertStatement.setString(2, upass);insertStatement.executeUpdate();}/*** 更新用户的加密后的密码。*/public void update(String uname, String upass) throws SQLException {updateStatement.setString(1, upass);updateStatement.setString(2, uname);updateStatement.executeUpdate();}/*** 查询用户的加密后的密码。*/public String queryPassword(String uname) throws SQLException {queryPasswordStatement.setString(1, uname);ResultSet rs = queryPasswordStatement.executeQuery();if (rs.next()) {return rs.getString("upass");}return null;}/*** 查询用户名是否已存在。*/public boolean queryUsernameExists(String uname) throws SQLException {queryUsernameStatement.setString(1, uname);ResultSet rs = queryUsernameStatement.executeQuery();return rs.next();}/*** 查询用户的登录失败次数。*/public int queryFailedLoginAttempts(String uname) throws SQLException {queryFailedAttemptsStatement.setString(1, uname);ResultSet rs = queryFailedAttemptsStatement.executeQuery();if (rs.next()) {return rs.getInt("failed_attempts");}return 0;}/*** 查询用户的最后登录失败时间。*/public Timestamp queryLastFailedLoginTime(String uname) throws SQLException {queryLastFailedTimeStatement.setString(1, uname);ResultSet rs = queryLastFailedTimeStatement.executeQuery();if (rs.next()) {return rs.getTimestamp("last_failed_time");}return null;}/*** 清除用户的登录失败记录。*/public void clearFailedLoginAttempts(String uname) throws SQLException {clearFailedAttemptsStatement.setString(1, uname);clearFailedAttemptsStatement.executeUpdate();}/*** 记录用户的登录失败尝试。*/public void recordFailedLoginAttempt(String uname, int failedAttempts, Date lastFailedTime) throws SQLException {recordFailedLoginAttemptStatement.setInt(1, failedAttempts);recordFailedLoginAttemptStatement.setTimestamp(2, new java.sql.Timestamp(lastFailedTime.getTime()));recordFailedLoginAttemptStatement.setString(3, uname);recordFailedLoginAttemptStatement.executeUpdate();}/*** 关闭数据库连接和预编译语句。*/public void submit() throws SQLException {if (insertStatement != null) insertStatement.close();if (updateStatement != null) updateStatement.close();if (queryPasswordStatement != null) queryPasswordStatement.close();if (queryUsernameStatement != null) queryUsernameStatement.close();if (queryFailedAttemptsStatement != null) queryFailedAttemptsStatement.close();if (queryLastFailedTimeStatement != null) queryLastFailedTimeStatement.close();if (clearFailedAttemptsStatement != null) clearFailedAttemptsStatement.close();if (recordFailedLoginAttemptStatement != null) recordFailedLoginAttemptStatement.close();if (conn != null) conn.close();}}

登录验证

package org.example;import java.io.*;
import java.sql.SQLException;
import java.util.Date;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;@WebServlet("/validate")
public class Validate extends HttpServlet {String uname = null;String upass = null;DBAccess dba;public void init() {dba = new DBAccess();dba.init(); // 初始化数据库连接和预编译语句}/*** 处理GET请求。* 当用户尝试登录时,通过GET请求触发此方法。*/public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {uname = request.getParameter("uname"); // 获取提交的用户名String temppass = request.getParameter("upass"); // 获取提交的密码try {if (isBlocked(uname)) {// 计算剩余限制时间并传递到前端long timeDifference = new Date().getTime() - dba.queryLastFailedLoginTime(uname).getTime();long timeWindowInMillis = 5 * 60 * 1000; // 15分钟float remainingTime = timeWindowInMillis - timeDifference;request.setAttribute("remainingTime", remainingTime);// 转发到 blocked.htmlrequest.getRequestDispatcher("blocked.jsp").forward(request, response);} else {String hashedPassword = dba.queryPassword(uname);if (hashedPassword != null && PasswordHashing.verifyPassword(temppass, hashedPassword)) {clearFailedLoginAttempts(uname); // 登录成功,清除登录失败记录response.sendRedirect("index.html"); // 跳转到主页} else {recordFailedLoginAttempt(uname); // 登录失败,记录失败次数和时间response.sendRedirect("error.html"); // 跳转到错误页面}}} catch (SQLException e) {throw new RuntimeException(e);}}/*** 处理POST请求。* 与GET请求处理相同,用于处理从登录页面提交的数据。*/public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response); // 调用与GET请求处理相同的方法}/*** 检查用户是否被阻止登录。* 如果用户的登录失败次数超过阈值,并且在时间窗口内,则返回 true,否则返回 false。* 如果时间窗口过期,清除登录失败记录。*/private boolean isBlocked(String uname) throws SQLException {int maxAttempts = 5; // 最大尝试次数int timeWindowInMinutes = 5; // 时间窗口(以分钟为单位)// 获取用户的登录失败次数和最后登录失败时间int failedAttempts = dba.queryFailedLoginAttempts(uname);Date lastFailedTime = dba.queryLastFailedLoginTime(uname);if (failedAttempts >= maxAttempts) {long timeDifference = new Date().getTime() - lastFailedTime.getTime();long timeWindowInMillis = timeWindowInMinutes * 60 * 1000; // 将分钟转换为毫秒if (timeDifference <= timeWindowInMillis) {return true; // 用户被阻止登录} else {clearFailedLoginAttempts(uname); // 清除登录失败记录,因为时间窗口已过}}return false; // 用户未被阻止登录}/*** 记录登录失败尝试。* 在数据库中增加用户的登录失败次数,并更新最后登录失败时间为当前时间。*/private void recordFailedLoginAttempt(String uname) throws SQLException {int failedAttempts = dba.queryFailedLoginAttempts(uname) + 1; // 增加登录失败次数Date now = new Date();java.sql.Date sqlDate = new java.sql.Date(now.getTime());dba.recordFailedLoginAttempt(uname, failedAttempts, sqlDate); // 在数据库中记录登录失败尝试}/*** 清除登录失败记录。* 在登录成功后,清除用户的登录失败次数和最后登录失败时间。*/private void clearFailedLoginAttempts(String uname) throws SQLException {dba.clearFailedLoginAttempts(uname); // 在数据库中清除登录失败记录}}

二、编码过滤器

浏览器默认使用UTF-8编码方式来发送数据。如果整个网站统一使用UTF-8编码,就不会
出现乱码。但如果使用汉字编码(如gbk或gb2312)就涉及编码转换。这时,我们可以使用HTTPServletRequest中的 setCharacterEncoding(String encoding)方法设置为指定的编码。

2.1过滤器基础

2.1.1工作原理

 

 2.1.2应用场合

用户权限的判断

如果一个Web客户端访问Web相关资源时,需要用户符合某些条件,那么可能需要在每个Web资源中添加对用户的权限判断,这是一件重复繁琐的事情,而且也不方便以后的系统维护,使用过滤器则可以简单地解决这个问题。


对请求内容进行统一编码

页面表单通常提交的数据编码是“ISO8859-1”,而对于中文系统来说,需要接收页面的中文输入,为了能够正确地获取页面的数据,需要在接收请求的资源中做编码设置与转换,在多个请求资源中都需要相同的操作,使用过滤器可以只需一次设置,整个 Web可用.


其他

除此之外,过滤器还有很多其他用途,例如XML转换过滤、数据压缩过滤、图像转换过滤、加密过滤、请求与响应封装等。

 2.1.3流程

 f

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/105664.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用海艺人工智能生成创意汉字

1、用某种字体生成文字。 jf storehttps://store.justfont.com/fonts 2、打开seaart。ai网站。https://www.seaart.ai/home 3、效果如下。 4、右键保存图片。

浏览器跨域

生活中的事跟跨域有什么关系&#xff0c;那必须有。 跨域的产生是浏览器的安全机制引起的&#xff0c;只有在使用Ajax时才会发生。简单来说就是你可以通过ajax发送请求&#xff0c;但要看远程服务器脸色&#xff0c;他没授权&#xff0c;浏览器这个老六就给拦截了&#xff0c;不…

【实操干货】如何开始用Qt Widgets编程?(三)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 在本文中&#xff0…

数据结构入门 — 链表详解_双向链表

前言 数据结构入门 — 双向链表详解* 博客主页链接&#xff1a;https://blog.csdn.net/m0_74014525 关注博主&#xff0c;后期持续更新系列文章 文章末尾有源码 *****感谢观看&#xff0c;希望对你有所帮助***** 系列文章 第一篇&#xff1a;数据结构入门 — 链表详解_单链表…

Windows 转 mac 记录

初次从Windows转mac可能会不适应&#xff0c;建议先看看 【6分钟搞定MacBook】不懂时无所适从&#xff0c;学会后越用越爽&#xff01;_哔哩哔哩_bilibili 我主要是做一些补充记录 1、Windows的右键等于mac的双击触控板、control单击触控板 2、运行中的应用下方会有一个点&…

深度学习:Sigmoid函数与Sigmoid层区别

深度学习&#xff1a;Sigmoid函数与Sigmoid层 1. Sigmoid神经网络层 vs. Sigmoid激活函数 在深度学习和神经网络中&#xff0c;“Sigmoid” 是一个常见的术语&#xff0c;通常用来表示两个相关但不同的概念&#xff1a;Sigmoid激活函数和Sigmoid神经网络层。这两者在神经网络…

go语言学习之有关变量的知识

文章目录 变量的学习1.变量的使用步骤2.变量的注意事项3.变量使用的三种方式&#xff1a;4.程序中 号的使用5.变量的数据类型1&#xff09;int数据类型2&#xff09;小数类型浮点型3&#xff09;**字符类型**4&#xff09;**字符串&#xff08;String&#xff09;类型**5&…

激活函数总结(十七):激活函数补充(PELU、Phish)

激活函数总结&#xff08;十七&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 Parametric Exponential Linear Unit&#xff08;PELU&#xff09;激活函数2.2 Phish激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、…

Spring Boot中使用validator如何实现接口入参自动检验

文章目录 一、背景二、使用三、举例 一、背景 在项目开发过程中&#xff0c;经常会对一些字段进行校验&#xff0c;比如字段的非空校验、字段的长度校验等&#xff0c;如果在每个需要的地方写一堆if else 会让你的代码变的冗余笨重且相对不好维护&#xff0c;如何更加规范和优…

网关认证的技术方案

我们认证授权使用springsecurity 和oauth2技术尽心实现具体实现流程见第五章文档&#xff0c;这里就是记录一下我们的技术方案 这是最开始的技术方案&#xff0c;我们通过认证为服务获取令牌然后使用令牌访问微服务&#xff0c;微服务解析令牌即可。但是缺点就是每个微服务都要…

听GPT 讲Prometheus源代码--util

Prometheus的util目录包含了一些通用的工具模块,主要包含以下文件: buckets.go 这个文件定义了一些常用的指标采样值范围(Quantile buckets),如:0.001,0.01,0.05,0.5,0.9,0.95,0.99,0.999等。这些buckets常用于计算指标的分位数线。 regex.go 这个文件定义了一些正则表达式匹配…

【在Windows下搭建Tomcat HTTP服务】

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

【ArcGIS微课1000例】0071:普通最小二乘法 (OLS)回归分析案例

严重声明:本文来自专栏《ArcGIS微课1000例:从点滴到精通》,为CSDN博客专家刘一哥GIS原创,原文及专栏地址为:(https://blog.csdn.net/lucky51222/category_11121281.html),谢绝转载或爬取!!! 文章目录 一、空间自回归模型二、ArcGIS普通最小二乘法回归(OLS)一、空间自…

VMware虚拟机连不上网络

固定ip地址 进入网络配置文件 cd /etc/sysconfig/network-scripts 打开文件 vi ifcfg-ens33 编辑 BOOTPROTO设置为static&#xff0c;有3个值&#xff08;decp、none、static&#xff09; BOOTPROTO"static" 打开网络 ONBOOT"yes" 固定ip IPADDR1…

AI智能语音机器人的基本业务流程

先画个图&#xff0c;了解下AI语音机器人的基本业务流程。 上图是一个AI语音机器人的业务流程&#xff0c;简单来说就是首先要配置话术&#xff0c;就是告诉机器人在遇到问题该怎么回答&#xff0c;这个不同公司不同行业的差别比较大&#xff0c;所以一般每个客户都会配置其个性…

Android项目如何上传Gitee仓库

前言 最近Android项目比较多&#xff0c;我都是把Android项目上传到Gitee中去&#xff0c;GitHub的话我用的少&#xff0c;可能我还是更喜欢Gitee吧&#xff0c;毕竟Gitee仓库用起来更加方便 一. 创建Gitee仓库 1. 先创建一个Gitee账号&#xff0c;然后登录上去 2. 创建Androi…

板卡设计+硬件每日学习十个知识点(44)23.8.24 (检测单元设计,接口部分设计,板卡电源输入设计,电源检测电路)

文章目录 1.检测单元介绍&#xff08;使用GD32单片机&#xff09;2.GD32的最小系统板3.GD32的温度监测4.GD32的电压监测和电流监测5.GD32的布线6.接口部分设计7.板卡电源输入设计8.电源检测电路 1.检测单元介绍&#xff08;使用GD32单片机&#xff09; 答&#xff1a; 首先要为…

融合算法综述

融合算法 前言一、概念二、原理三、融合的先决条件四、融合分类4.1、前融合和后融合4.2 、数据级融合、特征级融合和决策级融合 五、典型融合算法 多传感器信息融合&#xff08;Multi-sensor Information Fusion,MSIF&#xff09;&#xff1a;利用计算机技术将来自多传感器或多…

vue 弹出框 引入另一个vue页面

为什么要这么做,适用于在一个页面逻辑比较多的时候,可以搞多个页面,防止出错 index页面点击解约按钮,弹出框 进入jieyue.vue 核心代码 <el-buttonsize"mini"type"text"icon"el-icon-edit"v-if"scope.row.delFlag 0"click"j…

copy is all you need前向绘图 和疑惑标记

疑惑的起因 简化前向图 GPT4解释 这段代码实现了一个神经网络模型&#xff0c;包含了BERT、GPT-2和MLP等模块。主要功能是给定一个文本序列和一个查询序列&#xff0c;预测查询序列中的起始和结束位置&#xff0c;使其对应文本序列中的一个短语。具体实现细节如下&#xff1a…