Java 程序设计——登录系统
并发型服务器#
博客展示的登录系统的服务器端,将实现重复型服务器。
Client–server model#
客户端-服务器模型(Client–server model)简称C/S结构,是一种网络架构。大部分网络应用程序在编写时都假设一端是客户,另一端是服务器,其目的是为了让服务器为客户提供一些特定的服务。可以将这种服务分为两种类型:重复型或并发型。重复型服务器通过以下步骤进行交互:
- 等待一个客户请求的到来。
- 处理客户请求。
- 发送响应给发送请求的客户。
- 返回第 1 步。
并发型服务器采用以下步骤:
- 等待一个客户请求的到来。
- 启动一个新的服务器来处理这个客户的请求。在这期间可能生成一个新的进程、任务或线程,并依赖底层操作系统的支持。这个步骤如何进行取决于操作系统。生成的新服务器对客户的全部请求进行处理。处理结束后,终止这个新服务器。
- 返回第 1 步。
并发服务器的优点在于利用多线程来处理客户的请求,如果操作系统允许多任务,那么就可以同时为多个客户服务,本博客将实现并发型服务器。
Request/Response model#
请求/响应(Request/Response)模型一种通用的网络模型架构,该模型下永远都由客户端发起请求,由服务器进行响应并发送回响应报文。如果没有客户端进行请求或曾经请求过,那么服务器是无法将消息推送到客户端的。
三层架构#
本系统将基于三层架构实现,由表示层、逻辑层和存储层 3 层组成。表示层是应用的最高层,负责与用户进行交互,并将通信信息发送给逻辑层。逻辑层执行细节处理来控制应用的功能,连接到存储层。存储层则往往是数据库,用于检索并维护数据。
表示层向逻辑层发送请求,逻辑层对存储层完成检索和更新数据等操作,最后逻辑层根据存储层的数据对表示层进行响应。从概念上看,三层架构是一种线性关系。
登录系统设计#
登录系统由客户端和服务器 2 个部分组成。
客户端#
客户端需要接收用户输入的用户名和密码,然后将这 2 个信息发送给服务器。发送完毕之后等待服务器的响应信息,若在一定的时间内收到了服务器的响应,并且响应“操作成功”,则完成用户的登录操作。除了支持用户的登录操作,客户端还应该支持其他基于用户的操作,例如注册用户、密码修改和注销用户等。
- 修改密码和注销用户的操作,不能在登录前被调用,只能在登录之后。
服务器#
无论客户端是否启动,服务器应当保持启动的状态,并且持续监听分配给服务器的端口。当服务器收到客户端发送的数据时,服务器对数据进行处理并执行对应的操作。根据服务器的执行情况,若操作成功则向客户端通告操作成功,否则通告失败,接着继续 监听客户端发送的数据。
数据库连接#
用户名和密码信息将保存在数据库中,当服务器接收到客户端的数据时,将按照客户端请求的操作与数据库进行交互。根据客户端请求的方式不同,服务器与数据库交互时需要执行的操作为:
用户请求 | 数据库操作 |
---|---|
注册 | INSERT INTO |
登录 | SELECT |
密码修改 | UPDATE |
用户注销 | DELETE |
客户端实现#
UserBehaviorDAO 接口#
由于客户端和服务器的交互方式不仅仅只有三层架构的实现方式,还可以实现多层架构等其他方式。此时服务器及其它层的动作对于客户端来说是不可见的,因此定义 UserBehaviorDAO 接口支持客户端和服务器的交互方式的更换。
/**
* UserBehaviorDAO 接口指定了针对用户的行为
* @author 乌漆WhiteMoon
* @version 1.0
*/
public interface UserBehaviorDAO {
/**
* 这个方法将实现用户的注册操作
* @param username 用户名,String
* @param password 密码,String
* @return 操作是否成功,boolean
*/
public static boolean registerUser(String username, String password) {
return false;
}
/**
* 这个方法将实现用户的改密码操作
* @param username 用户名,String
* @param password 密码,String
* @param new_password 新密码,String
* @return 操作是否成功,boolean
*/
public static boolean changePassword(String username, String password, String new_password) {
return false;
}
/**
* 这个方法将实现用户的登录操作
* @param username 用户名,String
* @param password 密码,String
* @return 操作是否成功,boolean
*/
public static boolean signIn(String username, String password) {
return false;
}
/**
* 这个方法将实现用户的销户操作
* @param username 用户名,String
* @param password 密码,String
* @return 操作是否成功,boolean
*/
public static boolean cancelUser(String username,String password) {
return false;
}
}
UserBehavior 类#
UserBehavior 类是静态类,实现了 UserBehaviorDAO 接口,该类的 4 个方法将把数据传输给套接字进行和服务器的通信。
服务器实现#
UserDao 类#
由于服务器需要与数据库进行交互,而数据库应该作为一个可替换组件存在,因此定义 UserDao 接口指定了与数据库的交互行为。
/**
* SqlActionDao 接口指定了与数据库的交互行为
* @author 乌漆WhiteMoon
* @version 1.0
*/
public interface UserDao {
/**
* 查找用户名是否已存在,用户注册时用
* @param username 被查找的用户名
* @return true为用户名不存在,false为用户名已存在
* @throws SQLException 数据库异常
*/
public static boolean selectUsername(String username) throws SQLException{
return false;
}
/**
* 核对用户名和密码是否存在且匹配,用户登录和其他增删改操作时使用
* @param username 用户名
* @param password 密码
* @return true为用户名和密码存在且匹配,false为用户名或密码错误
* @throws SQLException 数据库异常
*/
public static boolean checkUser(String username, String password) throws SQLException{
return false;
}
/**
* 向数据库插入一个uesr记录,注册操作时用,调用该方法前应使用selectUsername()方法检查
* @param username 用户名
* @param password 密码
* @return true为记录插入成功,false为插入失败
* @throws SQLException 数据库异常
*/
public static boolean insertUser(String username, String password) throws SQLException{
return false;
}
/**
* 删除一条记录,注销用户时用,调用该方法前应使用checkUser()方法检查
* @param username 用户名
* @return true为删除成功,false删除失败
* @throws SQLException 数据库异常
*/
public static boolean deleteUser(String username) throws SQLException{
return false;
}
/**
* 更新一个uesr的password字段,改密码操作时用,调用该方法前应使用checkUser()方法检查
* @param username 用户名
* @param new_password 新密码,用于替换原有的条目
* @return true为更换成功,false更换失败
* @throws SQLException 数据库异常
*/
public static boolean updateUserPasswd(String username, String new_password) throws SQLException{
return false;
}
}
UserImpl 类#
UserImpl 类是静态类,实现了 UserDao 接口,该类将连接到 MySQL 数据库进行对数据的操作。为了支持对数据库的连接,还需要 MysqlConnect 类完成连接操作。
数据封装#
操作码和分隔符#
客户端发送的有效载荷为一个字符串,该字符串由操作码、用户名和密码 3 个部分组成,3 个部分之间用 “+” 连接。
服务器接收到数据之后,将数据按照分隔符 “+” 进行分割。
//分割明文,执行对应的操作
String result_set[] = result_decode.split("+");
通过操作码执行对应的操作,操作码和用户的请求的关系如下:
用户请求 | 数据库操作 |
---|---|
1 | 注册 |
2 | 登录 |
3 | 密码修改 |
4 | 用户注销 |
- 改密码操作还需要传输新密码,因此“密码”部分的内容为 “password '+' newpassword”。
数据加密#
MD5Util 类#
MD5Util 类只有 getMD5Str() 方法,用于对传入的字符串进行 MD5 加密。MD5Util 类会被 UserBehavior 类调用,传输的用户名和密码都会进行 MD5 加密,从而保证这 2 者的安全性。
例如对用户名“张三”和密码“123456”调用 getMD5Str() 方法进行加密,输出结果为:
615db57aa314529aaa0fbe95b3e95bd3
e10adc3949ba59abbe56e057f20f883e
Base 64 加密#
即使对用户名和密码进行加密,攻击者仍然可能截取密文进行提交,为了保证安全性需要对整个有效载荷进行加密。UserClient 类的 sendRequest 方法使用 base64 加密,加密的代码为:
//明文写入操作码,跟着用户名即密码
String Plaintext = actionCode + " " + username + " " + password;
//对明文进行 base64加密
String ciphertext = Base64.getEncoder().encodeToString(Plaintext.getBytes("utf-8"));
Base 64 解密#
服务器接收到数据之后要先对数据进行 Base 64 解密,解密的代码如下:
DataInputStream in = new DataInputStream(server.getInputStream());
//将客户端传来的密文转成明文
String result_decode = new String(Base64.getDecoder().decode(in.readUTF()));
MySQL 数据库#
users 表#
数据库中的 users 表用于存储 MD5 加密后的用户名和密码,users 表的字段有。
下图是存储了 3 个用户信息的 users 表。
SQL 查询语句#
selectUsername() 方法#
这个方法将基于select查找用户名是否已存在,用户注册时用。
SELECT username FROM users WHERE binary username = '%s';
checkUser() 方法#
这个方法将基于 select 核对用户名和密码是否存在且匹配,用户登录和其他增删改操作时使用.
SELECT username,password FROM users WHERE binary username = '%s' AND password = '%s';
insertUser() 方法#
这个方法将基于 insert 向数据库插入一个 uesr 记录,注册操作时用,调用该方法前应使用
selectUsername() 方法检查。
INSERT INTO users(username, password) values('%s','%s');
deleteUser() 方法#
这个方法将基于 delete 删除一条记录,注销用户时用,调用该方法前应使用 checkUser() 方法检查。
"DELETE FROM users WHERE username = '%s';"
updateUserPasswd() 方法#
这个方法将基于 update 向更新一个 uesr 的 password 字段,改密码操作时用,调用该方法前应使用 checkUser() 方法检查。
UPDATE users SET password='%s' WHERE username = '%s';
Socket 实现#
Socket#
不同端系统的进程是通过彼此之间向套接字发送报文来实现通信,套接字就好比是门禁,想要和应用程序进行通信需要先通过门禁的验证。同理也不是什么报文都能随意出门的,必须是得到允许的报文才会被送出门去。
为了连接主机,我们需要目标主机的 IP 地址,这样才能知道要发给哪个端系统,就像送信就一定要有收件人。但是由于一台主机上可能运行着好多个进程,需要指定一个端口号,令指定的进程接收分组。需要强调的是,我们自己写的端口需要避开 RFC 定义的协议,例如 HTTP 协议的端口号 80。
Client-Socket#
UserClient 类#
UserClient 类是基于请求响应模型的客户端套接字,该类应该在客户端被调用,只有 sendRequest() 发送报文一个方法。
Server-Socket#
Response 类#
本类的方法将接受套接字收到的数据,调用 SqlActionDao 接口执行对客户端请求的操作,操作完成后进行响应。
UserServer 类#
UserServer 类将继承 Thread 类,run() 方法将保持对分配给该进程的端口的监听,若接收到数据则调用 Response 类中的方法进行操作。
Customer 类#
用户登录之后,将会把登录的用户信息实例化一个 Customer 类。注意 Customer 类不会保存用户的密码,安全的做法是让用户执行改密码和注销操作时都额外提供一次密码。
public class Customer {
private final String username;
private final String username_md5;
private LinkedList<Emails> Inbox; //收件箱
private LinkedList<Emails> Outbox; //发件箱
/**
* 这个方法是customer对象的构造器
* @param username 用户名,String
* @return customer对象
*/
public Customer(String username) {
this.username = username;
this.username_md5 = MD5Util.getMD5Str(username);
}
}
GUI 设计#
登录界面#
注册用户界面#
密码修改界面#
销户界面#
参考资料#
《计算机网络(第七版)》 谢希仁 著,电子工业出版社
《TCP/IP 详解 卷1:协议》[美]W.Richard Stevens 著,范建华 胥光辉 张涛 等译,谢希仁 校,机械工业出版社
《SQL注入攻击与防御(第2版)》 [美]Justin Clarke 著,施宏斌 叶愫 译,清华大学出版社
计算机网络:协议栈分层
应用层:HTTP 协议
HTTP请求/响应模型
Java DAO 模式
应用层:UDP 套接字编程
应用层:TCP 套接字编程
MySQL——SELECT
MySQL——增、删、改
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)