Java 程序設計——登錄系統


並發型服務器

博客展示的登錄系統的服務器端,將實現重復型服務器。

Client–server model

客戶端-服務器模型(Client–server model)簡稱C/S結構,是一種網絡架構。大部分網絡應用程序在編寫時都假設一端是客戶,另一端是服務器,其目的是為了讓服務器為客戶提供一些特定的服務。可以將這種服務分為兩種類型:重復型或並發型。重復型服務器通過以下步驟進行交互:

  1. 等待一個客戶請求的到來。
  2. 處理客戶請求。
  3. 發送響應給發送請求的客戶。
  4. 返回第 1 步。

並發型服務器采用以下步驟:

  1. 等待一個客戶請求的到來。
  2. 啟動一個新的服務器來處理這個客戶的請求。在這期間可能生成一個新的進程、任務或線程,並依賴底層操作系統的支持。這個步驟如何進行取決於操作系統。生成的新服務器對客戶的全部請求進行處理。處理結束后,終止這個新服務器。
  3. 返回第 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——增、刪、改


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM