Web服务器端应用开发

news2024/12/28 9:26:47

一、登录验证器

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"; // 数据库连接URL
    private 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"; // 数据库连接URL
    private 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.html
                request.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.coloradmin.cn/o/929491.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型是什么?泰迪大模型能够解决企业哪些痛点?

什么是大模型&#xff1f; 大模型是指模型具有庞大的参数规模和复杂程度的机器学习模型。在深度学习领域&#xff0c;大模型通常是指具有数百万到数十亿参数的神经网络模型。这些模型需要大量的计算资源和存储空间来训练和存储&#xff0c;并且往往需要进行分布式计算和特殊…

Distuptor高性能的无锁框架

目录 Distuptor介绍初识Distuptor数据结构等待策略 Distuptor性能预分配内存使用cpu-cache数据结构 Distuptor使用Distuptor配置步骤单/多生产者策略 Distuptor介绍 初识Distuptor 它是一种高性能的无锁框架&#xff0c;适用于高并发业务场景&#xff0c;其实内部也就是一个生…

Error running ‘Tomcat 8.5.29‘ Address localhost:1099 is already in use

一、Error running ‘Tomcat 8.5.29’ Address localhost:1099 is already in use 原因&#xff1a;端口1099被占用了。 二、解决 2.1 解决方法一-结束该端口1099占用 //1-查看端口占用&#xff0c;根据端口号1099&#xff0c;获取PID(进程ID) netstat -ano | findstr "…

探索未来世界,解密区块链奥秘!

你是否曾好奇&#xff0c;区块链是如何影响着我们的生活与未来&#xff1f;想要轻松了解这个引领着技术革命的概念吗&#xff1f;那么这本令人着迷的新书《区块链导论》绝对值得你拥有&#xff01; 内容丰富多彩&#xff0c;让你轻松掌握&#xff1a; **1章&#xff1a;区块链…

断路器灭弧室真空度试验

试验目的 真空开关的绝缘和灭弧介质是真空&#xff0c;真空灭弧室成品出厂时灭弧室内压强一般要求在10-5Pa以上。随着存放和运行时间的增加&#xff0c;灭弧室内的压强会逐渐增加&#xff0c;使灭弧室最终失效。 真空灭弧室内压强升高的原因:一方面是由于机械损坏导致波纹管破…

【高危】Kubernetes Windows节点kubernetes-csi-proxy提权漏洞 (CVE-2023-3893)

zhi.oscs1024.com​​​​​ 漏洞类型OS命令注入发现时间2023-08-24漏洞等级高危MPS编号MPS-t6rg-974fCVE编号CVE-2023-3893漏洞影响广度小 漏洞危害 OSCS 描述Kubernetes是开源的容器管理平台&#xff0c;kubernetes-csi-proxy是用于Windows中的CSI&#xff08;容器存储接口&…

搭建HAProxy + Keepalived高可用

安装 在四台虚拟机上&#xff0c;我们以如下方式搭建集群&#xff1a; 192.168.115.3 haproxykeepalived 192.168.115.4haproxykeepalived 192.168.115.5 nginx 192.168.115.6 nginx 在192.168.115.3 和192.168.115.4 上安装haproxy和keepalived&#xff08;haproxy编译安装…

C++ set和map的基本使用

set和map介绍 set和map是C STL中的关联式容器&#xff0c;关联式容器是容器里面存储的是<key, value>结构的 键值对&#xff0c;在数据检索时比序列式容器效率更高 键值对&#xff1a; 用来表示具有一一对应关系的一种结构&#xff0c;该结构中一般只包含两个成员变量ke…

【Qt专栏】实现单例程序,禁止程序多开的几种方式

目录 一&#xff0c;简要介绍 二&#xff0c;实现示例&#xff08;Windows&#xff09; 1.使用系统级别的互斥机制 2.通过共享内存&#xff08;进程间通信-IPC&#xff09; 3.使用命名互斥锁&#xff08;不推荐&#xff09; 4.使用文件锁 5.通过网络端口检测 一&#xf…

visual studio 2022.NET Core 3.1 未显示在目标框架下拉列表中

问题描述 在Visual Studio 2022我已经安装了 .NET core 3.1 并验证可以运行 .NET core 3.1 应用程序&#xff0c;但当创建一个新项目时&#xff0c;目标框架的下拉列表只允许 .NET 6.0和7.0。而我在之前用的 Visual Studio 2019&#xff0c;可以正确地添加 .NET 核心项目。 …

Dev-C++ 下载和安装

TOC 1. 下载 Dev-C 访问 sourceforge 网站&#xff0c;然后单击 Download&#xff0c; 2. 安装 Dev-C 双击下载的安装文件进行安装。 3. 在线环境 https://cpp.sh/ 完结&#xff01;

IDEA中GIT相关操作

文章目录 IDEA中GIT相关操作IDEA TAG 提交查看某个tag版本代码、根据某个tag创建分支将分支代码合并到主分支 IDEA中GIT相关操作 IDEA TAG 提交 1.创建tag 右键项目&#xff0c;然后按照 Git -> Repository -> Tag… 创建tag。 或 顶部菜单栏 按照 VCS -> Git->…

27- v-model 原理 组件应用

v-model 原理 原理: V-model本质上是一个语法糖。例如应用在输入框上&#xff0c;就是 value属性 和 input事件 的合写 作用: 提供数据的双向绑定 (1) 数据变,视图跟着变 : value (2) 试图变,数据跟着变: input 注意: $event 用于在模板中, 获取事件的形参 <template>…

YOLOv5算法改进(5)— 添加ECA注意力机制

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。ECA注意力机制是一种用于图像处理中的注意力机制&#xff0c;是在通道注意力机制的基础上做了进一步的改进。通道注意力机制主要是通过提取权重&#xff0c;作用在原特征图的通道维度上&#xff0c;而ECA注意力机制则使用了…

人力资源小程序的设计方案与实现

随着互联网的发展&#xff0c;人才招聘已经成为许多企业的一项重要任务。为了提高招聘效率和便利求职者&#xff0c;许多企业开始采用小程序作为招聘平台。本文将为大家介绍一个搭建本地人才招聘网小程序的实用指南。 首先&#xff0c;我们需要登录【乔拓云】制作平台&#xff…

信创国产系统麒麟arm架构中nginx安装过程

前言 在事业单位或国企&#xff0c;信创项目在步步推进&#xff0c;下面将在国产系统通信arm架构中nginx的安装过程记录分享出来&#xff0c;希望帮助到有需要的小伙伴。 1、nginx下载 1.1、在线下载 进入指定目录&#xff0c;如/usr/local&#xff0c;执行如下命令&#x…

DataFrame.plot函数详解(二)

DataFrame.plot函数详解&#xff08;二&#xff09; 1. Line 1.1主要参数 import matplotlib.pyplot as plt import pandas as pd import numpy as npdf pd.Series(abs(np.random.randn(10)), index pd.date_range(2020-01-01, periods 10)) df.plot.line(style :,marker…

【51单片机】EEPROM-IIC实验(按键控制数码管)

目录 &#x1f381;I2C总线 ​编辑 &#x1f381;代码 &#x1f3f3;️‍&#x1f308;main.c &#x1f3f3;️‍&#x1f308;i2.c &#x1f386;代码分析 &#x1f381;I2C总线 I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线&#xff0c;主要用于近距…

xsschallenge通关(1-10)

文章目录 level1level 2level 3level 4level 5level 6level 7level 8level9level 10 level1 这一关很简单&#xff0c;标准的xss注入&#xff0c;打开hackbar&#xff0c;输入 <script>alert(/xss/)</script>点击EXECUTE&#xff0c;通关&#xff01; level 2 这…