1. 需求分析
管理後臺的登錄的唯一目的就是去驗證用戶的合法性,即根據輸入的用戶名和密碼能否在數據庫中找到相應的用戶。因此,登錄頁需要做的最重要的工作就是驗證。驗證可以分爲前端和後端兩個方面進行:
- 前端:不允許不輸入用戶名或密碼來直接登錄,如果用戶在這兩項中有一項沒有輸入相應的信息,前端頁面應該要給出相應的提示;如果根據輸入的用戶名和密碼無法在數據庫中找到合法的用戶,那麼也應該在前端頁面給出相應的提示,告訴用戶用戶名或密碼錯誤
- 後端:如果用戶正常的輸入了用戶名和密碼,那麼接下來就需要根據用戶名和密碼到數據庫中進行驗證,那麼就要求在數據持久層應該有相應的方法。如果根據用戶名和密碼能找到合法的用戶,那麼將跳轉到歡迎頁,否則在前端給出錯誤提示
在分析了前端和後端對於用戶登錄的需求後,我們就可以分別對其進行處理,從而實現用戶登錄驗證管理功能。
2. 前端驗證
登錄頁較爲簡單,本質上就是一個form表單
<div class="m-container-small m-padded-tb-massive" style="max-width: 30em !important;">
<div class="ur container">
<div class="ui middle aligned center aligned grid">
<div class="column">
<h2 class="ui teal image header">
<div class="content">
管理後臺登錄
</div>
</h2>
<form class="ui large form" method="post" th:action="@{/admin/login}">
<div class="ui segment">
<!--用戶名輸入框-->
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" name="username" placeholder="用戶名">
</div>
</div>
<!--用戶名輸入框-->
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" name="password" placeholder="密碼">
</div>
</div>
<button class="ui fluid large teal submit button">登 錄</button>
</div>
<div class="ui error mini message"></div>
<div class="ui mini negative message" th:unless="${#strings.isEmpty(message)}" th:text="${message}">用戶名或密碼錯誤</div>
</form>
</div>
</div>
</div>
</div>
對於前端來說,主要工作是進行進行表單的驗證,不允許用戶輸入空的信息,針對的部分就是<div class="ui segment">
所定義的form表單。因此,可以通過JS對錶單進行驗證,如下所示:
<script>
$('.ui.form').form({
fields : {
username : {
identifier: 'username',
rules: [{
type : 'empty',
prompt: '請輸入用戶名'
}]
},
password : {
identifier: 'password',
rules: [{
type : 'empty',
prompt: '請輸入密碼'
}]
}
}
});
</script>
首先通過類'.ui.form'
找到form表單,然後對於其中的兩個field區域,即用戶名輸入和密碼輸入定義規則:
- 如果用戶名爲空,則前端提示請輸入用戶名
- 如果密碼爲空,則前端提示請輸入密碼
整體上最後是這樣的一個效果,當我不輸入任何信息時,頁面就會彈出信息,另外對應的框會變紅,提示這是一個不合法的登錄請求。
如果用戶名或密碼錯誤,同樣會給出提示信息。
3. 後端驗證
首先在表現層應該有一個Controller來處理登錄請求,然後使用localhost:8080/admin
就可以返回登錄頁,如下所示:
@Controller
@RequestMapping("/admin")
public class LoginController {
@Autowired
private UserService userService;
@GetMapping
public String loginPage(){
return "admin/login";
}
}
程序會在resource目錄及其子目錄下找名爲login.html
的頁面進行加載。
而在form表單中使用thymleaf定義的動作是th:action="@{/admin/login}
,因此需要一個方法處理/admin/login爲鏈接的請求,進行用戶驗證,如下所示
@Controller
@RequestMapping("/admin")
public class LoginController {
@Autowired
private UserService userService;
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session, RedirectAttributes attributes){
...
}
}
首先使用@PostMapping表示該方法處理的一個post請求,然後使用@RequestParam從request域中拿到前端輸入的用戶名(username)和密碼(password)。同時使用HttpSession在session中記錄登錄的用戶,使用RedirectAttributes實現重定向,當用戶名或密碼輸入錯誤時,仍然重定向回登錄頁。
那麼拿到用戶名和密碼後,在業務層需要使用它們到數據庫中找有沒有相應的用戶。因此,業務層需要定義checkUser()
來進行驗證。
public interface UserService {
User checkUser(String username, String password);
}
接口的實現類爲:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User checkUser(String username, String password) {
// 控制檯顯示信息
System.out.println(username + " " + password);
// 驗證
User user = userRepository.findByUsernameAndPassword(username, password);
return user;
}
}
那麼在持久層就需要相應的操作方法,根據Jpa的命名規則findByxxxAndxxx
定義相應的方法findByUsernameAndPassword()
,只需要定義相應的方法即可,並不需要自己寫處理邏輯,Jpa會自動的幫我們處理:
public interface UserRepository extends JpaRepository<User,Long> {
User findByUsernameAndPassword(String username, String password);
}
不熟悉Jpa可以查閱我之前的一篇博文:一文詳盡 Spring Data JPA 的日常使用
再次回到業務層,當調用UserRepository中的方法查詢用戶後會得到一個User對象。如果可以找到,那麼user不爲空,否則user就等於null,表示該用戶不存在。因此,最終表現層通過調用業務層的方法就能拿到一個user,其中處理post請求的方法處理邏輯如下所示:
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session, RedirectAttributes attributes){
// 拿到User對象
User user = userService.checkUser(username, password);
// 如果當前user合法
if (user != null){
// 那麼在session中保存user,同時爲了安全將password置空
user.setPassword(null);
session.setAttribute("user", user);
// 跳轉到歡迎頁
return "admin/index";
} else {
// 如果當前的user爲空,說明用戶名或密碼錯誤
// 使用attributes向前端傳遞提示message
attributes.addFlashAttribute("message", "用戶名或密碼錯誤");
// 重定向回登錄頁
return "redirect:/admin";
}
}
這樣後端的驗證工作就處理結束了,主要就是驗證用戶名和密碼在數據庫中是否存在。另外,通常爲了密碼的安全,嘗試用MD5進行加密,這樣數據庫中保存的密碼其實是加密後的結果。
public class MD5Utils {
public static String code(String str){
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
byte[]byteDigest = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < byteDigest.length; offset++) {
i = byteDigest[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
//32位加密
return buf.toString();
// 16位的加密
//return buf.toString().substring(8, 24);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}
使用MD5加密後,checkUser()
就變成了如下的形式:
@Override
public User checkUser(String username, String password) {
System.out.println(username + " " + password);
User user = userRepository.findByUsernameAndPassword(username, MD5Utils.code(password));
return user;
}
即傳遞到數據庫中的也是加密後的結果。如果一切順利通過,那麼最終會正常跳轉到後臺的歡迎頁。
另外,還需要一個註銷功能,當用戶點擊右上角的註銷時會退出登錄,重新回到登錄頁。因此,導航欄的註銷按鈕部分就需要使用th:href
添加一個跳轉
<i class="dropdown icon"></i>
<div class="menu">
<a href="#" th:href="@{/admin/logout}" class="item">註銷</a>
</div>
然後表現層需要有方法來處理,即將之前保存在session域中的user清除掉,最後重定向會登錄頁。
@GetMapping("/logout")
public String logout(HttpSession session){
session.removeAttribute("user");
return "redirect:/admin";
}
4. 登錄歡迎頁
登錄歡迎頁只是簡單的展示一個頁面,並不會處理其他的請求,頁面設計如下:
<div class="m-container-small m-padded-tb-big">
<div class="ui container">
<div class="ui success large message">
<h3 th:text="'Hi, ' + ${session.user.nickname}+' ,歡迎回來!'">Hi, Forlogen,歡迎登錄!</h3>
</div>
<img src="../static/images/manba.jpg" th:src="@{/images/manba.jpg}" alt=""
class="ui rounded bordered fluid image">
</div>
</div
這裏需要從session域中拿到user對象的nickname字段,然後使用th:text
進行動態的替換;而顯示的圖片使用th:src
來指定圖片存放的路徑。