個人博客系統后端 - 注冊登錄功能實現指南

一、功能概述

個人博客系統的注冊登錄功能包括:

  1. 用戶注冊:新用戶可以通過提供用戶名、密碼、郵箱等信息創建賬號
  2. 用戶登錄:已注冊用戶可以通過用戶名和密碼進行身份驗證,獲取JWT令牌
  3. 身份驗證:使用JWT令牌訪問需要認證的API

二、技術棧

  • 后端框架:Spring Boot 3.2.5
  • 安全框架:Spring Security
  • 數據庫:MySQL 8.0
  • 認證方式:JWT (JSON Web Token)
  • API測試工具:Postman

三、實現步驟

1. 數據庫設計

用戶表(users)設計:

CREATE TABLE users (id BIGINT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(100) NOT NULL,email VARCHAR(100) NOT NULL UNIQUE,nickname VARCHAR(50),role VARCHAR(20) NOT NULL DEFAULT 'USER',status INT NOT NULL DEFAULT 1,created_at DATETIME NOT NULL,updated_at DATETIME NOT NULL
);

2. 實體類設計

User實體類:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false, unique = true, length = 50)private String username;@Column(nullable = false, length = 100)private String password;@Column(nullable = false, unique = true, length = 100)private String email;@Column(length = 50)private String nickname;@Column(nullable = false, length = 20)private String role = "USER"; // 默認角色@Column(nullable = false)private Integer status = 1; // 默認狀態(1為激活)@Column(name = "created_at", nullable = false, updatable = false)private LocalDateTime createdAt;@Column(name = "updated_at", nullable = false)private LocalDateTime updatedAt;@PrePersistprotected void onCreate() {createdAt = LocalDateTime.now();updatedAt = LocalDateTime.now();}@PreUpdateprotected void onUpdate() {updatedAt = LocalDateTime.now();}
}

3. DTO設計

注冊DTO:

public class RegisterUserDto {@NotBlank(message = "用戶名不能為空")@Size(min = 4, max = 50, message = "用戶名長度必須在4-50個字符之間")private String username;@NotBlank(message = "密碼不能為空")@Size(min = 6, max = 100, message = "密碼長度必須在6-100個字符之間")private String password;@NotBlank(message = "郵箱不能為空")@Email(message = "郵箱格式不正確")private String email;private String nickname;// getters and setters
}

登錄DTO:

public class LoginUserDto {@NotBlank(message = "用戶名不能為空")private String username;@NotBlank(message = "密碼不能為空")private String password;// getters and setters
}

4. 數據倉庫接口

@Repository
public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);Optional<User> findByEmail(String email);boolean existsByUsername(String username);boolean existsByEmail(String email);
}

5. 服務層實現

AuthService接口:

public interface AuthService {User registerUser(RegisterUserDto registerUserDto);Map<String, Object> loginUser(LoginUserDto loginUserDto) throws AuthenticationException;
}

AuthServiceImpl實現類:

@Service
public class AuthServiceImpl implements AuthService {private final UserRepository userRepository;private final PasswordEncoder passwordEncoder;private final AuthenticationManager authenticationManager;private final JwtUtil jwtUtil;@Autowiredpublic AuthServiceImpl(UserRepository userRepository,PasswordEncoder passwordEncoder,AuthenticationManager authenticationManager,JwtUtil jwtUtil) {this.userRepository = userRepository;this.passwordEncoder = passwordEncoder;this.authenticationManager = authenticationManager;this.jwtUtil = jwtUtil;}@Override@Transactionalpublic User registerUser(RegisterUserDto registerUserDto) {// 檢查用戶名是否已存在if (userRepository.existsByUsername(registerUserDto.getUsername())) {throw new UserAlreadyExistsException("用戶名 " + registerUserDto.getUsername() + " 已被注冊");}// 檢查郵箱是否已存在if (registerUserDto.getEmail() != null && !registerUserDto.getEmail().isEmpty() && userRepository.existsByEmail(registerUserDto.getEmail())) {throw new UserAlreadyExistsException("郵箱 " + registerUserDto.getEmail() + " 已被注冊");}// 創建新用戶實體User newUser = new User();newUser.setUsername(registerUserDto.getUsername());// 加密密碼newUser.setPassword(passwordEncoder.encode(registerUserDto.getPassword()));newUser.setEmail(registerUserDto.getEmail());newUser.setNickname(registerUserDto.getNickname());// 使用默認值(role="USER", status=1)// createdAt 和 updatedAt 由 @PrePersist 自動處理// 保存用戶到數據庫return userRepository.save(newUser);}@Overridepublic Map<String, Object> loginUser(LoginUserDto loginUserDto) throws AuthenticationException {// 創建認證令牌UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUserDto.getUsername(), loginUserDto.getPassword());// 進行認證Authentication authentication = authenticationManager.authenticate(authenticationToken);SecurityContextHolder.getContext().setAuthentication(authentication);// 獲取用戶詳情UserDetails userDetails = (UserDetails) authentication.getPrincipal();// 生成JWT令牌String jwt = jwtUtil.generateToken(userDetails);// 獲取用戶IDUser user = userRepository.findByUsername(loginUserDto.getUsername()).orElseThrow(() -> new RuntimeException("用戶不存在"));// 創建返回結果Map<String, Object> result = new HashMap<>();result.put("token", jwt);result.put("userId", user.getId());result.put("username", user.getUsername());result.put("expiresIn", 604800L); // 默認7天 = 604800秒return result;}
}

6. 控制器實現

@RestController
@RequestMapping("/auth")
public class AuthController {private final AuthService authService;@Autowiredpublic AuthController(AuthService authService) {this.authService = authService;}@PostMapping("/register")public ResponseEntity<?> registerUser(@Valid @RequestBody RegisterUserDto registerUserDto) {User registeredUser = authService.registerUser(registerUserDto);Map<String, Object> response = new HashMap<>();response.put("code", HttpStatus.CREATED.value());response.put("message", "注冊成功");return ResponseEntity.status(HttpStatus.CREATED).body(response);}@PostMapping("/login")public ResponseEntity<?> loginUser(@Valid @RequestBody LoginUserDto loginUserDto) {try {Map<String, Object> loginResult = authService.loginUser(loginUserDto);Map<String, Object> response = new HashMap<>();response.put("code", HttpStatus.OK.value());response.put("message", "登錄成功");Map<String, Object> data = new HashMap<>();data.put("token", loginResult.get("token"));data.put("userId", loginResult.get("userId"));data.put("username", loginResult.get("username"));data.put("expiresIn", loginResult.get("expiresIn"));response.put("data", data);return ResponseEntity.ok(response);} catch (BadCredentialsException e) {Map<String, Object> response = new HashMap<>();response.put("code", HttpStatus.UNAUTHORIZED.value());response.put("message", "用戶名或密碼錯誤");return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);} catch (Exception e) {Map<String, Object> response = new HashMap<>();response.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());response.put("message", "服務器內部錯誤: " + e.getMessage());return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);}}@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Map<String, Object> handleValidationExceptions(MethodArgumentNotValidException ex) {Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));Map<String, Object> response = new HashMap<>();response.put("code", HttpStatus.BAD_REQUEST.value());response.put("message", "請求參數錯誤");response.put("errors", errors);return response;}@ExceptionHandler(UserAlreadyExistsException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Map<String, Object> handleUserAlreadyExistsException(UserAlreadyExistsException ex) {Map<String, Object> response = new HashMap<>();response.put("code", HttpStatus.BAD_REQUEST.value());response.put("message", ex.getMessage());return response;}
}

7. 安全配置

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {@Autowiredprivate UserDetailsServiceImpl userDetailsService;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(AbstractHttpConfigurer::disable).exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)).accessDeniedHandler((request, response, accessDeniedException) -> response.setStatus(HttpStatus.FORBIDDEN.value()))).authorizeHttpRequests(authz -> authz.requestMatchers("/auth/**").permitAll().anyRequest().authenticated()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));return http.build();}
}

8. JWT工具類

@Component
public class JwtUtil {@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private Long expiration;public String generateToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>();return createToken(claims, userDetails.getUsername());}private String createToken(Map<String, Object> claims, String subject) {Date now = new Date();Date expiryDate = new Date(now.getTime() + expiration);return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(now).setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, secret).compact();}// 其他JWT驗證方法...
}

四、使用Postman測試注冊登錄功能

1. 測試用戶注冊

  1. 創建POST請求

    • URL: http://localhost:8080/auth/register
    • 請求頭: Content-Type: application/json
    • 請求體:
    {"username": "testuser","password": "Password123","email": "testuser@example.com","nickname": "測試用戶"
    }
    
  2. 發送請求并驗證響應

    • 成功響應(201 Created):
    {"code": 201,"message": "注冊成功"
    }
    
    • 失敗響應(400 Bad Request):
    {"code": 400,"message": "用戶名 testuser 已被注冊"
    }
    

2. 測試用戶登錄

  1. 創建POST請求

    • URL: http://localhost:8080/auth/login
    • 請求頭: Content-Type: application/json
    • 請求體:
    {"username": "testuser","password": "Password123"
    }
    
  2. 發送請求并驗證響應

    • 成功響應(200 OK):
    {"code": 200,"message": "登錄成功","data": {"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","userId": 1,"username": "testuser","expiresIn": 604800}
    }
    
    • 失敗響應(401 Unauthorized):
    {"code": 401,"message": "用戶名或密碼錯誤"
    }
    

3. 使用JWT令牌訪問受保護的API

  1. 創建請求(例如獲取用戶信息):

    • URL: http://localhost:8080/users/1
    • 請求頭: Authorization: Bearer {token}(使用登錄時獲取的token)
  2. 發送請求并驗證響應

五、常見問題及解決方案

1. Java 9+中缺少javax.xml.bind問題

問題描述:在Java 9及以上版本中使用JJWT 0.9.1庫時,可能會遇到以下錯誤:

java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter

原因:從Java 9開始,Java EE模塊(包括javax.xml.bind包)被移除出了JDK核心。

解決方案:在pom.xml中添加JAXB API依賴:

<!-- 添加JAXB API依賴,解決Java 9+中缺少javax.xml.bind問題 -->
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version>
</dependency>
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.3.1</version>
</dependency>
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>2.3.0.1</version>
</dependency>

2. API路徑不匹配問題

問題描述:README文檔中描述的API路徑與實際代碼中的路徑不匹配。

原因:README中描述的基礎路徑是http://localhost:8080/api/v1,但控制器中只配置了/auth路徑。

解決方案

  1. 方案一:使用正確的URL:http://localhost:8080/auth/registerhttp://localhost:8080/auth/login

  2. 方案二:在application.properties中添加上下文路徑配置:

    server.servlet.context-path=/api/v1
    

    這樣就可以使用README中描述的URL:http://localhost:8080/api/v1/auth/registerhttp://localhost:8080/api/v1/auth/login

3. 數據庫連接問題

問題描述:注冊接口返回成功,但數據庫中沒有保存數據。

可能原因

  1. 數據庫名稱配置錯誤
  2. 事務回滾(可能由未捕獲的異常引起)
  3. 數據庫連接問題

解決方案

  1. 檢查application.properties中的數據庫配置是否正確:

    spring.datasource.url=jdbc:mysql://localhost:3306/weblog?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    spring.datasource.username=root
    spring.datasource.password=123456
    
  2. 確保數據庫存在并且可以連接

  3. 檢查日志中是否有事務回滾的錯誤信息

4. 請求驗證失敗

問題描述:注冊或登錄請求返回400錯誤,但沒有明確的錯誤信息。

可能原因:請求體中缺少必填字段或格式不正確。

解決方案

  1. 確保請求體中包含所有必填字段
  2. 確保字段格式正確(例如,郵箱格式、密碼長度等)
  3. 檢查控制臺日志,查看詳細的驗證錯誤信息

六、最佳實踐

  1. 密碼安全

    • 始終使用BCrypt等安全的密碼哈希算法
    • 不要在響應中返回密碼,即使是加密后的密碼
    • 設置密碼復雜度要求(長度、特殊字符等)
  2. JWT安全

    • 使用強密鑰(至少256位)
    • 設置合理的過期時間
    • 考慮實現令牌刷新機制
    • 在生產環境中使用HTTPS
  3. 異常處理

    • 為不同類型的異常提供明確的錯誤消息
    • 不要在生產環境中暴露敏感的技術細節
    • 使用統一的響應格式
  4. 日志記錄

    • 記錄關鍵操作(注冊、登錄、登出)
    • 記錄異常和錯誤
    • 不要記錄敏感信息(密碼、令牌等)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/76987.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76987.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/76987.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

投行交易與風控系統的消費側冪等架構設計與實戰

1.背景和痛點 1.1 資金操作敏感性場景 核心需求&#xff1a; 交易唯一性&#xff1a;資金類操作必須保證全局唯一執行計算原子性&#xff1a;風控指標計算需具備事務性特征審計追溯&#xff1a;所有操作需保留完整冪等軌跡 1.2 業務損失統計 二、技術挑戰與架構設計 2.1 分…

odoo-046 視圖顯示的 name 數據庫中存儲的不一樣

文章目錄 一、問題由來二、排查經過1. 問 deepseek2. 驗證3. 新問題 三、 總結四、補充&#xff08;翻譯模型 ir.translation 中 src 和 value 字段詳解&#xff09; 一、問題由來 客戶有多個公司&#xff0c;使用多個數據庫。他們有時需要同步不同數據庫之間的數據的需求。在…

充電寶項目:規則引擎Drools學習

文章目錄 規則引擎 Drools1 問題2 規則引擎概述2.1 規則引擎2.2 使用規則引擎的優勢2.3 規則引擎應用場景2.4 Drools介紹 3 Drools入門案例3.1 創建springboot項目 引入依賴3.2 添加Drools配置類3.4 創建實體類Order3.5 orderScore.drl3.6 編寫測試類 4 Drools基礎語法4.1 規則…

HTML、CSS 和 JavaScript 常見用法及使用規范

一、HTML 深度剖析 1. 文檔類型聲明 HTML 文檔開頭的 <!DOCTYPE html> 聲明告知瀏覽器當前文檔使用的是 HTML5 標準。它是文檔的重要元信息&#xff0c;能確保瀏覽器以標準模式渲染頁面&#xff0c;避免怪異模式下的兼容性問題。 2. 元數據標簽 <meta> 標簽&am…

基于CNN+ViT的蔬果圖像分類實驗

本文只是做一個簡單融合的實驗&#xff0c;沒有任何新穎&#xff0c;大家看看就行了。 1.數據集 本文所采用的數據集為Fruit-360 果蔬圖像數據集&#xff0c;該數據集由 Horea Mure?an 等人整理并發布于 GitHub&#xff08;項目地址&#xff1a;Horea94/Fruit-Images-Datase…

Ubuntu24.04安裝libgl1-mesa-glx 報錯,軟件包缺失

在 Ubuntu 24.04 系統中&#xff0c;您遇到的 libgl1-mesa-glx 軟件包缺失問題可能是由于該包在最新的 Ubuntu 版本中被重命名為 libglx-mesa0。以下是針對該問題的詳細解決方案&#xff1a; 1. 問題原因分析 包名稱變更&#xff1a;在 Ubuntu 24.04 中&#xff0c;libgl1-me…

webpack vite

? 1、webpack webpack打包工具&#xff08;重點在于配置和使用&#xff0c;原理并不高優。只在開發環境應用&#xff0c;不在線上環境運行&#xff09;&#xff0c;壓縮整合代碼&#xff0c;讓網頁加載更快。 前端代碼為什么要進行構建和打包&#xff1f; 體積更好&#x…

如何在爬蟲中合理使用海外代理?在爬蟲中合理使用海外ip

我們都知道&#xff0c;爬蟲工作就是在各類網頁中游走&#xff0c;快速而高效地采集數據。然而如果目標網站分布在多個國家或者存在區域性限制&#xff0c;那靠普通的網絡訪問可能會帶來諸多阻礙。而這時&#xff0c;“海外代理”儼然成了爬蟲工程師們的得力幫手&#xff01; …

數據倉庫分層存儲設計:平衡存儲成本與查詢效率

數據倉庫分層存儲不僅是一個技術問題,更是一種藝術:如何在有限的資源下,讓數據既能快速響應查詢,又能以最低的成本存儲? 目錄 一、什么是數據倉庫分層存儲? 二、分層存儲的體系架構 1. 數據源層(ODS,Operational Data Store) 2. 數據倉庫層(DW,Data Warehouse)…

YOLO學習筆記 | 基于YOLOv8的植物病害檢測系統

以下是基于YOLOv8的植物病害檢測系統完整技術文檔,包含原理分析、數學公式推導及代碼實現框架。 基于YOLOv8的智能植物病害檢測系統研究 摘要 針對傳統植物病害檢測方法存在的效率低、泛化性差等問題,本研究提出一種基于改進YOLOv8算法的智能檢測系統。通過設計輕量化特征提…

高級語言調用C接口(二)回調函數(4)Python

前面2篇分別說了java和c#調用C接口&#xff0c;參數為回調函數&#xff0c;回調函數中參數是結構體指針。 接下來說下python的調用方法。 from ctypes import * import sysclass stPayResult(Structure):_pack_ 4 # 根據實際C結構體的對齊方式設置&#xff08;常見值為1,4,…

springboot啟動動態定時任務

1.自定義定時任務線程池 package com.x.devicetcpserver.global.tcp.tcpscheduler;import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotatio…

pytorch框架認識--手寫數字識別

手寫數字是機器學習中非常經典的案例&#xff0c;本文將通過pytorch框架&#xff0c;利用神經網絡來實現手寫數字識別 pytorch中提供了手寫數字的數據集&#xff0c;我們可以直接從pytorch中下載 MNIST中包含70000張手寫數字圖像&#xff1a;60000張用于訓練&#xff0c;10000…

WPF 使用依賴注入后關閉窗口程序不結束

原因是在ViewModel中在構造函數中注入了Window 對象&#xff0c;即使沒有使用&#xff0c;主窗口關閉程序不會退出&#xff0c;即使 ViewModel 是 AddTransient 注入的。 解決方法&#xff1a;不使用構造函數注入Window&#xff0c;通過GetService獲取Window 通過注入對象調用…

用戶管理(添加和刪除,查詢信息,切換用戶,查看登錄用戶,用戶組,配置文件)

目錄 添加和刪除用戶 查詢用戶信息 切換用戶 查看當前的操作用戶是誰 查看首次登錄的用戶是誰 用戶組&#xff08;對屬于同個角色的用戶統一管理&#xff09; 新增組 刪除組 添加用戶的同時&#xff0c;指定組 修改用戶的組 組的配置文件&#xff08;/etc/group&…

PyTorch學習-小土堆教程

網絡搭建torch.nn.Module 卷積操作 torch.nn.functional.conv2d(input, weight, biasNone, stride1, padding0, dilation1, groups1) 神經網絡-卷積層

MVCC詳細介紹及面試題

目錄 1.什么是mvcc&#xff1f; 2.問題引入 3. MVCC實現原理&#xff1f; 3.1 隱藏字段 3.2 undo log 日志 3.2.1 undo log版本鏈 3.3 readview 3.3.1 當前讀 ?編輯 3.3.2 快照讀 3.3.3 ReadView中4個核心字段 3.3.4 版本數據鏈訪問的規則&#xff08;了解&#x…

企業級Active Directory架構設計與運維管理白皮書

企業級Active Directory架構設計與運維管理白皮書 第一章 多域架構設計與信任管理 1.1 企業域架構拓撲設計 1.1.1 林架構設計規范 林根域規劃原則&#xff1a; 采用三段式域名結構&#xff08;如corp.enterprise.com&#xff09;&#xff0c;避免使用不相關的頂級域名架構主…

android11 DevicePolicyManager淺析

目錄 &#x1f4d8; 簡單定義 &#x1f4d8;應用啟用設備管理者 &#x1f4c2; 文件位置 &#x1f9e0; DevicePolicyManager 功能分類舉例 &#x1f6e1;? 1. 安全策略控制 &#x1f4f7; 2. 控制硬件功能 &#x1f9f0; 3. 應用管理 &#x1f512; 4. 用戶管理 &am…

Java學習手冊:Java線程安全與同步機制

在Java并發編程中&#xff0c;線程安全和同步機制是確保程序正確性和數據一致性的關鍵。當多個線程同時訪問共享資源時&#xff0c;如果不加以控制&#xff0c;可能會導致數據不一致、競態條件等問題。本文將深入探討Java中的線程安全問題以及解決這些問題的同步機制。 線程安…