个人博客系统的注册登录功能包括:
用户表(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
);
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;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
注册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
}
@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);
}
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;
@Autowired
public AuthServiceImpl(UserRepository userRepository,
PasswordEncoder passwordEncoder,
AuthenticationManager authenticationManager,
JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.authenticationManager = authenticationManager;
this.jwtUtil = jwtUtil;
}
@Override
@Transactional
public 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);
}
@Override
public 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);
// 获取用户ID
User 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;
}
}
@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;
@Autowired
public 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;
}
}
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public 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();
}
}
@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验证方法...
}
创建POST请求:
http://localhost:8080/auth/register
Content-Type: application/json
{
"username": "testuser",
"password": "Password123",
"email": "[email protected]",
"nickname": "测试用户"
}
发送请求并验证响应:
{
"code": 201,
"message": "注册成功"
}
{
"code": 400,
"message": "用户名 testuser 已被注册"
}
创建POST请求:
http://localhost:8080/auth/login
Content-Type: application/json
{
"username": "testuser",
"password": "Password123"
}
发送请求并验证响应:
{
"code": 200,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userId": 1,
"username": "testuser",
"expiresIn": 604800
}
}
{
"code": 401,
"message": "用户名或密码错误"
}
创建请求(例如获取用户信息):
http://localhost:8080/users/1
Authorization: Bearer {token}
(使用登录时获取的token)发送请求并验证响应
问题描述:在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依赖:
<dependency>
<groupId>javax.xml.bindgroupId>
<artifactId>jaxb-apiartifactId>
<version>2.3.1version>
dependency>
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-implartifactId>
<version>2.3.1version>
dependency>
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-coreartifactId>
<version>2.3.0.1version>
dependency>
问题描述:README文档中描述的API路径与实际代码中的路径不匹配。
原因:README中描述的基础路径是http://localhost:8080/api/v1
,但控制器中只配置了/auth
路径。
解决方案:
方案一:使用正确的URL:http://localhost:8080/auth/register
和http://localhost:8080/auth/login
方案二:在application.properties中添加上下文路径配置:
server.servlet.context-path=/api/v1
这样就可以使用README中描述的URL:http://localhost:8080/api/v1/auth/register
和http://localhost:8080/api/v1/auth/login
问题描述:注册接口返回成功,但数据库中没有保存数据。
可能原因:
解决方案:
检查application.properties中的数据库配置是否正确:
spring.datasource.url=jdbc:mysql://localhost:3306/weblog?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456
确保数据库存在并且可以连接
检查日志中是否有事务回滚的错误信息
问题描述:注册或登录请求返回400错误,但没有明确的错误信息。
可能原因:请求体中缺少必填字段或格式不正确。
解决方案:
密码安全:
JWT安全:
异常处理:
日志记录: