feat(auth): add user registration endpoint
- Validate username, email and password on register - Reject duplicate usernames and email addresses - Create user with LOCAL credential and default USER role Closes #3
This commit is contained in:
4 files changed
+128
-2
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
package com.onixbyte.deltaforceguide.controller;
|
package com.onixbyte.deltaforceguide.controller;
|
||||||
|
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.LoginRequest;
|
import com.onixbyte.deltaforceguide.domain.dto.LoginRequest;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.dto.RegisterRequest;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.UserResponse;
|
import com.onixbyte.deltaforceguide.domain.dto.UserResponse;
|
||||||
import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth;
|
import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth;
|
||||||
import com.onixbyte.deltaforceguide.service.AuthService;
|
import com.onixbyte.deltaforceguide.service.AuthService;
|
||||||
@@ -46,4 +47,11 @@ public class AuthController {
|
|||||||
.header(HttpHeaders.SET_COOKIE, expiredCookie.toString())
|
.header(HttpHeaders.SET_COOKIE, expiredCookie.toString())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(description = "用户注册")
|
||||||
|
@PostMapping("/register")
|
||||||
|
public ResponseEntity<UserResponse> register(@Validated @RequestBody RegisterRequest request) {
|
||||||
|
var user = authService.register(request);
|
||||||
|
return authService.getUserResponseResponseEntity(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.domain.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration request containing the new user account information.
|
||||||
|
*
|
||||||
|
* @author zihluwang
|
||||||
|
*/
|
||||||
|
@Schema(description = "注册请求")
|
||||||
|
public record RegisterRequest(
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
String username,
|
||||||
|
@NotBlank(message = "邮箱不能为空")
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
@Schema(description = "邮箱", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
String email,
|
||||||
|
@Schema(description = "昵称")
|
||||||
|
String nickname,
|
||||||
|
@Schema(description = "头像链接")
|
||||||
|
String avatarUrl,
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
String password
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.onixbyte.deltaforceguide.manager;
|
package com.onixbyte.deltaforceguide.manager;
|
||||||
|
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.User;
|
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.UserRole;
|
||||||
import com.onixbyte.deltaforceguide.repository.UserRepository;
|
import com.onixbyte.deltaforceguide.repository.UserRepository;
|
||||||
|
import com.onixbyte.deltaforceguide.repository.UserRoleRepository;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -17,9 +19,11 @@ import java.util.Optional;
|
|||||||
public class UserManager {
|
public class UserManager {
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final UserRoleRepository userRoleRepository;
|
||||||
|
|
||||||
public UserManager(UserRepository userRepository) {
|
public UserManager(UserRepository userRepository, UserRoleRepository userRoleRepository) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
|
this.userRoleRepository = userRoleRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,5 +87,42 @@ public class UserManager {
|
|||||||
public Optional<User> findByUsernameOrEmail(String principal) {
|
public Optional<User> findByUsernameOrEmail(String principal) {
|
||||||
return userRepository.findByUsernameOrEmail(principal);
|
return userRepository.findByUsernameOrEmail(principal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a username is already registered.
|
||||||
|
*
|
||||||
|
* @param username the username to check
|
||||||
|
* @return true if the username is already taken
|
||||||
|
*/
|
||||||
|
public boolean existsByUsername(String username) {
|
||||||
|
return userRepository.existsByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an email address is already registered.
|
||||||
|
*
|
||||||
|
* @param email the email address to check
|
||||||
|
* @return true if the email address is already taken
|
||||||
|
*/
|
||||||
|
public boolean existsByEmail(String email) {
|
||||||
|
return userRepository.existsByEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists a new user together with an initial role assignment in a single transaction.
|
||||||
|
*
|
||||||
|
* @param user the user to persist, whose credentials are cascaded
|
||||||
|
* @param role the initial role name to assign
|
||||||
|
* @return the saved user
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public User createWithRole(User user, String role) {
|
||||||
|
var saved = userRepository.save(user);
|
||||||
|
userRoleRepository.save(UserRole.builder()
|
||||||
|
.user(saved)
|
||||||
|
.role(role)
|
||||||
|
.build());
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,12 +2,18 @@ package com.onixbyte.deltaforceguide.service;
|
|||||||
|
|
||||||
import com.onixbyte.deltaforceguide.client.TokenClient;
|
import com.onixbyte.deltaforceguide.client.TokenClient;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.LoginRequest;
|
import com.onixbyte.deltaforceguide.domain.dto.LoginRequest;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.dto.RegisterRequest;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.UserResponse;
|
import com.onixbyte.deltaforceguide.domain.dto.UserResponse;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.User;
|
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
|
||||||
|
import com.onixbyte.deltaforceguide.exeption.BadRequestException;
|
||||||
import com.onixbyte.deltaforceguide.exeption.InternalServerErrorException;
|
import com.onixbyte.deltaforceguide.exeption.InternalServerErrorException;
|
||||||
import com.onixbyte.deltaforceguide.manager.CookieManager;
|
import com.onixbyte.deltaforceguide.manager.CookieManager;
|
||||||
|
import com.onixbyte.deltaforceguide.manager.UserManager;
|
||||||
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
|
import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication;
|
||||||
import com.onixbyte.deltaforceguide.shared.CookieName;
|
import com.onixbyte.deltaforceguide.shared.CookieName;
|
||||||
|
import com.onixbyte.deltaforceguide.shared.CredentialProvider;
|
||||||
|
import com.onixbyte.deltaforceguide.shared.Role;
|
||||||
import org.jspecify.annotations.NonNull;
|
import org.jspecify.annotations.NonNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -15,6 +21,7 @@ import org.springframework.http.HttpCookie;
|
|||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@@ -32,15 +39,21 @@ public class AuthService {
|
|||||||
private final AuthenticationManager authenticationManager;
|
private final AuthenticationManager authenticationManager;
|
||||||
private final TokenClient tokenClient;
|
private final TokenClient tokenClient;
|
||||||
private final CookieManager cookieManager;
|
private final CookieManager cookieManager;
|
||||||
|
private final UserManager userManager;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public AuthService(
|
public AuthService(
|
||||||
AuthenticationManager authenticationManager,
|
AuthenticationManager authenticationManager,
|
||||||
TokenClient tokenClient,
|
TokenClient tokenClient,
|
||||||
CookieManager cookieManager
|
CookieManager cookieManager,
|
||||||
|
UserManager userManager,
|
||||||
|
PasswordEncoder passwordEncoder
|
||||||
) {
|
) {
|
||||||
this.authenticationManager = authenticationManager;
|
this.authenticationManager = authenticationManager;
|
||||||
this.tokenClient = tokenClient;
|
this.tokenClient = tokenClient;
|
||||||
this.cookieManager = cookieManager;
|
this.cookieManager = cookieManager;
|
||||||
|
this.userManager = userManager;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,6 +79,41 @@ public class AuthService {
|
|||||||
return authentication.getDetails();
|
return authentication.getDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new user account with the LOCAL password provider and the default USER role.
|
||||||
|
*
|
||||||
|
* @param request the registration information
|
||||||
|
* @return the newly created {@link User}
|
||||||
|
*/
|
||||||
|
public User register(RegisterRequest request) {
|
||||||
|
var username = request.username().trim();
|
||||||
|
var email = request.email().trim();
|
||||||
|
|
||||||
|
if (userManager.existsByUsername(username)) {
|
||||||
|
throw new BadRequestException("用户名已被注册。");
|
||||||
|
}
|
||||||
|
if (userManager.existsByEmail(email)) {
|
||||||
|
throw new BadRequestException("该邮箱已被注册。");
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = LocalDateTime.now();
|
||||||
|
var user = User.builder()
|
||||||
|
.username(username)
|
||||||
|
.email(email)
|
||||||
|
.nickname(request.nickname())
|
||||||
|
.avatarUrl(request.avatarUrl())
|
||||||
|
.createdAt(now)
|
||||||
|
.updatedAt(now)
|
||||||
|
.build();
|
||||||
|
var credential = UserCredential.builder()
|
||||||
|
.provider(CredentialProvider.LOCAL)
|
||||||
|
.credential(passwordEncoder.encode(request.password()))
|
||||||
|
.build();
|
||||||
|
user.addCredential(credential);
|
||||||
|
|
||||||
|
return userManager.createWithRole(user, Role.ROLE_USER);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public ResponseEntity<UserResponse> getUserResponseResponseEntity(User user) {
|
public ResponseEntity<UserResponse> getUserResponseResponseEntity(User user) {
|
||||||
var currentTime = LocalDateTime.now();
|
var currentTime = LocalDateTime.now();
|
||||||
|
|||||||
Reference in new issue
Block a user