个人博客系统后端 - 注册登录功能实现指南

一、功能概述

个人博客系统的注册登录功能包括:

  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.mzph.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中的线程安全问题以及解决这些问题的同步机制。 线程安…