Spring boot 搭建個人博客系統(二)——登錄註冊功能
一直想用Spring boot 搭建一個屬於自己的博客系統,剛好前段時間學習了葉神的牛客項目課受益匪淺,乘熱打鐵也主要是學習,好讓自己熟悉這類項目開發的基本流程。系統採用Spring boot+MyBatis+MySQL的框架進行項目開發。
項目源碼:Jblog
個人主頁:tuzhenyu’s page
原文地址:Spring boot 搭建個人博客系統(二)——登錄註冊功能
0. 思路
用戶登錄註冊功能主要實現用戶的添加,驗證和記住密碼一段時間內的免密碼登錄。用戶的註冊是往數據庫中插入用戶的用戶名和密碼等信息,用戶的驗證是從數據庫中取出用戶的用戶名和密碼等信息進行比對。明文密碼存儲有很大的風險,採用在密碼後加salt再經過MD5加密的形式存儲,這樣一方面避免了用戶密碼信息泄露的風險,同時也防止了暴力破解密碼的可能。
登錄成功後生成一串字符作爲ticket存儲在數據庫和cookie中,用於下次登錄的免密碼登錄驗證。一段時間後數據庫中的ticket失效,用戶需要重新登錄。
1. 數據模型
系統登錄註冊功能需要驗證用戶信息,需要操縱數據庫的user表和login_ticket表,使用MyBatis作爲系統的ORM框架用來簡化數據操作。
1.1 引入MyBatis ORM框架
(1) 在pom.xml中添加MyBatis依賴jar包和MySQL連接相關的jar包,引入MyBatis相關類庫和數據庫連接相關類庫。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
(2) 添加MyBatis配置文件,設置MyBatis相關參數
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="defaultStatementTimeout" value="3000"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="useGeneratedKeys" value="true"/>
</settings>
</configuration>
(3) 在系統配置文件application.properities中配置數據庫URL,用戶名密碼等信息,同時設置MyBatis配置文件位置。
spring.datasource.url=
spring.datasource.username=
#spring.datasource.password=
spring.datasource.password=
mybatis.config-location=classpath:mybatis-config.xml
1.2 添加數據庫表實體類
根據user表的具體字段設置實體類的對應字段,MyBatis的mapUnderscoreToCamelCase參數設定爲true,說明數據庫字段的下劃線分割自動對應實體類的駝峯形式。
public class User {
private int id;
private String name;
private String password;
private String salt;
private String headUrl;
private String role;
public User(){}
public User(String name){
this.name = name;
this.password = "";
this.salt = "";
this.headUrl = "";
this.role = "user";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getHeadUrl() {
return headUrl;
}
public void setHeadUrl(String headUrl) {
this.headUrl = headUrl;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
根據login_ticket表的具體字段設置實體類的對應字段
public class LoginTicket {
private int id;
private int userId;
private Date expired;
private int status;
private String ticket;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public Date getExpired() {
return expired;
}
public void setExpired(Date expired) {
this.expired = expired;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
}
1.3 添加數據庫操作DAO類
根據MyBatis的使用特點,創建數據庫操作接口UserDao.class和LoginTicket.class,用作對數據庫執行具體的操作。
@Mapper
public interface UserDao {
String TABLE_NAEM = " user ";
String INSERT_FIELDS = " name, password, salt, head_url ,role ";
String SELECT_FIELDS = " id, " + INSERT_FIELDS;
@Insert({"insert into",TABLE_NAEM,"(",INSERT_FIELDS,") values (#{name},#{password},#{salt},#{headUrl},#{role})"})
public void insertUser(User user);
@Select({"select",SELECT_FIELDS,"from",TABLE_NAEM,"where id=#{id}"})
public User seletById(int id);
@Select({"select",SELECT_FIELDS,"from",TABLE_NAEM,"where name=#{name}"})
public User seletByName(@Param("name") String name);
@Delete({"delete from",TABLE_NAEM,"where id=#{id}"})
public void deleteById(int id);
}
@Mapper
public interface LoginTicketDao {
String TABLE_NAEM = " login_ticket ";
String INSERT_FIELDS = " user_id, ticket, expired, status ";
String SELECT_FIELDS = " id, " + INSERT_FIELDS;
@Insert({"insert into",TABLE_NAEM,"(",INSERT_FIELDS,") values (#{userId},#{ticket},#{expired},#{status})"})
void insertLoginTicket(LoginTicket loginTicket);
@Select({"select",SELECT_FIELDS,"from",TABLE_NAEM,"where id=#{id}"})
LoginTicket seletById(int id);
@Select({"select",SELECT_FIELDS,"from",TABLE_NAEM,"where ticket=#{ticket}"})
LoginTicket seletByTicket(String ticket);
@Update({"update",TABLE_NAEM,"set status = #{status} where ticket = #{ticket}"})
void updateStatus(@Param("ticket") String ticket, @Param("status") int status);
@Delete({"delete from",TABLE_NAEM,"where id=#{id}"})
void deleteById(int id);
}
2. 用戶註冊
ORM框架的作用就是將對象與關係型數據庫的表進行關聯性綁定,根據插入的對象解析出對應的字段插入數據庫中。用戶註冊是在驗證註冊用戶名密碼可用的情況下生成一個User對象插入數據庫中,同時爲了保護用戶信息安全在密碼存儲時,隨機生成固定長度的字符串作爲salt與密碼組合後講過MD5加密存儲在數據庫字段中。
用戶密碼如果直接散列後存儲在數據庫中,黑客可以通過獲得這個密碼散列值,然後通過查散列值字典(彩虹表)的方式暴力破解,得到用戶的密碼;通過加salt加密的方式可以一定程度上解決這一問題,因爲salt值由系統隨機生成,也只有系統知道。即便黑客獲取了密碼的散列值但在不知道salt值的前提下暴力破解散列值的機率大大降低。
加salt加密並不能完全杜絕用戶密碼的泄露,因爲一旦密碼散列值和salt值同時泄露,黑客通過salt值重建彩虹表,依舊能夠獲取用戶密碼。只是這樣的計算成本會大大增加,假設每個用戶一個salt, 散列值字典的字段有10萬, 一次基於salt值的字典重建就要重新生成10萬個字段,那麼10個密碼就需要生成10個散列字典,也就是100萬 個字段。因此,從某種意義上來講,增加salt的長度也就增加了散列值字典字段的數目,也可以提高安全性。還有一種提高安全性的方式,就是salt值的動態生成。通過一定算法動態生成salt值,這樣可以大大降低salt值泄露的風險。
public Map<String,String> register(String username, String password){
Map<String,String> map = new HashMap<>();
Random random = new Random();
if (StringUtils.isBlank(username)){
map.put("msg","用戶名不能爲空");
return map;
}
if (StringUtils.isBlank(password)){
map.put("msg","密碼不能爲空");
return map;
}
User u = userDao.seletByName(username);
if (u!=null){
map.put("msg","用戶名已經被佔用");
return map;
}
User user = new User();
user.setName(username);
user.setSalt(UUID.randomUUID().toString().substring(0,5));
user.setHeadUrl(String.format("https://images.nowcoder.com/head/%dm.png",random.nextInt(1000)));
user.setPassword(JblogUtil.MD5(password+user.getSalt()));
user.setRole("user");
userDao.insertUser(user);
String ticket = addLoginTicket(user.getId());
map.put("ticket",ticket);
return map;
}
3. 用戶登錄
用戶登錄主要是進行用戶名和密碼的驗證,由於用戶在註冊時候會生成隨機的salt值進行密碼存儲加密,在密碼驗證時需要讀取用戶對應的salt值組合進行散列化後與數據庫中用戶密碼進行比對,如果一致則登錄成功。
用戶登錄時明文傳輸密碼存在風險,黑客可以通過抓包的方式截取用戶信息。一般爲了降低這種Web應用登錄密碼傳輸過程中泄露的風險,可以採用https方式傳輸和通過公匙和私匙的非對稱加密的方式。
public Map<String,String> login(String username, String password){
Map<String,String> map = new HashMap<>();
Random random = new Random();
if (StringUtils.isBlank(username)){
map.put("msg","用戶名不能爲空");
return map;
}
if (StringUtils.isBlank(password)){
map.put("msg","密碼不能爲空");
return map;
}
User u = userDao.seletByName(username);
if (u==null){
map.put("msg","用戶名不存在");
return map;
}
if (!JblogUtil.MD5(password+u.getSalt()).equals(u.getPassword())){
map.put("msg","密碼錯誤");
return map;
}
String ticket = addLoginTicket(u.getId());
map.put("ticket",ticket);
return map;
}
4. 免密碼登錄
免密碼登錄功能主要是通過一種自動身份驗證的方式實現。用戶登錄或註冊成功後,在一定時間內(如2個小時)再次訪問同一個Web程序的任一個頁面時都無需再次登錄,而是直接進入界面(僅限於本機)。實現這個功能關鍵就是服務端要識別客戶的身份。使用Cookie是最簡單的身從驗證。
Cookie是web服務器存放在客戶端的一個文件,客戶端訪問特定URL時會查詢該文件,將與該URL相關的Cookie字段傳輸至服務端用作特定處理。Cookie可以設置失效時間,當Cookie過了失效時間後會自動消失不再隨請求傳輸到服務器。
用戶在登錄成功或註冊成功後隨機生成一個ticket作爲用戶後續操作無需密碼驗證的憑證,往login_ticket表中插入一條記錄將ticket與具體的用戶綁定,這樣用戶在操作時候就能通過ticket憑證辨別身份。登錄成功或註冊成功後將ticket憑證放入Cookie,保存在用戶瀏覽器中,在下次訪問時候會隨請求傳輸到服務器用作身份驗證。
(1) 往login_ticket表中插入一條記錄用於綁定ticket憑證和用戶身份
public String addLoginTicket(int userId){
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(userId);
Date date = new Date();
date.setTime(date.getTime()+1000*3600*30);
loginTicket.setExpired(date);
loginTicket.setStatus(0);
loginTicket.setTicket(UUID.randomUUID().toString().replaceAll("-",""));
loginTicketDao.insertLoginTicket(loginTicket);
return loginTicket.getTicket();
}
(2) 登錄成功後將ticket憑證放入cookie,保存在用戶瀏覽器;ticket有時效,過憑證期後用戶需重新登錄。
@RequestMapping("/login")
public String login(Model model, HttpServletResponse httpResponse,
@RequestParam String username,@RequestParam String password,@RequestParam(value = "next",required = false)String next){
Map<String,String> map = userService.login(username,password);
if (map.containsKey("ticket")) {
Cookie cookie = new Cookie("ticket",map.get("ticket"));
cookie.setPath("/");
httpResponse.addCookie(cookie);
if (StringUtils.isNotBlank(next)){
return "redirect:"+next;
}
return "redirect:/";
}else {
model.addAttribute("msg", map.get("msg"));
return "login";
}
}
(3) 註冊成功後將ticket憑證放入cookie,保存在用戶瀏覽器;ticket有時效,過憑證期後用戶需重新登錄。
@RequestMapping("/register")
public String register(Model model, HttpServletResponse httpResponse,
@RequestParam String username, @RequestParam String password
,@RequestParam(value = "next",required = false)String next){
Map<String,String> map = userService.register(username,password);
if (map.containsKey("ticket")){
Cookie cookie = new Cookie("ticket",map.get("ticket"));
cookie.setPath("/");
httpResponse.addCookie(cookie);
if (StringUtils.isNotBlank(next))
return "redirect:"+next;
else
return "redirect:/";
}else {
model.addAttribute("msg",map.get("msg"));
return "login";
}
}
5. 總結
系統的註冊登錄功能主要是將用戶註冊的用戶名密碼存儲在數據庫,在下次登錄時進行比對。同時爲了免密碼登錄,在登錄成功或註冊成功時後生成用戶憑證ticket與用戶身份綁定在一起放入cookie中,等用戶下次訪問時候隨請求傳輸至服務器進行驗證。這樣就能直接根據憑證識別用戶身份,無需再經過用戶名密碼的驗證。
版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/u013967175/article/details/77435178