Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,37 @@ public class SecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
private final JWTUtil jwtUtil;

//AuthenticationManager Bean ๋“ฑ๋ก
/****
* Provides the `AuthenticationManager` bean for authentication operations.
*
* @param configuration the authentication configuration used to obtain the manager
* @return the configured `AuthenticationManager` instance
* @throws Exception if the authentication manager cannot be retrieved
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}

/****
* Provides a BCryptPasswordEncoder bean for password hashing.
*
* @return a BCryptPasswordEncoder instance
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

/****
* Configures the application's security filter chain, including CORS, CSRF, authentication, authorization, and custom filters.
*
* Sets up CORS to allow requests from "http://localhost:3000" with all methods and headers, enables credential support, and exposes the "Authorization" header. Disables CSRF protection, form login, and HTTP Basic authentication. Defines authorization rules to permit access to login, signup, and root endpoints, restrict "/admin" to users with the "ADMIN" role, and require authentication for all other endpoints. Registers a custom login filter for handling authentication at "/api/auth/login" and a JWT filter for validating tokens. Configures session management to be stateless.
*
* @param http the HttpSecurity to configure
* @return the configured SecurityFilterChain
* @throws Exception if an error occurs during configuration
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

public class WebMvcConfig implements WebMvcConfigurer {
/****
* Configures CORS mappings to allow requests from "http://localhost:3000" for all URL paths.
*
* @param registry the CORS registry to which the mapping is added
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
@Slf4j
@RestControllerAdvice
public class ExceptionController {
/**
* Handles validation errors for method arguments and returns a structured error response.
*
* Returns an error response with HTTP 400 status, including details about each invalid field and its validation message.
*
* @param e the exception containing validation errors for method arguments
* @return an ErrorResponse with code "400", a generic bad request message, and validation error details
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResponse invalidRequestHandler(MethodArgumentNotValidException e) {
Expand All @@ -41,7 +49,12 @@ public ErrorResponse invalidRequestHandler(MethodArgumentNotValidException e) {
return response;
}

//@ResponseStatus(HttpStatus.NOT_FOUND)
/**
* Handles {@link CustomException} by returning a structured error response with the exception's status code and message.
*
* @param e the custom exception to handle
* @return a {@link ResponseEntity} containing the error response and the appropriate HTTP status code
*/
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> customException(CustomException e) {
int statusCode = e.getStatusCode();
Expand All @@ -57,6 +70,11 @@ public ResponseEntity<ErrorResponse> customException(CustomException e) {
return response;
}

/**
* Handles uncaught exceptions and returns a standardized 500 Internal Server Error response.
*
* @return a ResponseEntity containing an ErrorResponse with error code "500" and a generic server error message
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> exceptionHandler(Exception e) {
log.error("Exception: ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,26 @@ public class ErrorResponse {
private final String code;
private final String message;
private final Map<String, List<String>> validation = new HashMap<>();
/**
* Constructs an ErrorResponse with the specified status code and error message.
*
* @param code the status code representing the error type
* @param message a descriptive error message
*/
@Builder
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
}

/**
* Adds a validation error message for a specific field.
*
* If the field already has validation messages, the new message is appended to the list; otherwise, a new list is created for the field.
*
* @param fieldName the name of the field with a validation error
* @param errorMessage the validation error message to add
*/
public void addValidation(String fieldName, String errorMessage) {
this.validation.computeIfAbsent(fieldName, key -> new ArrayList<>())
.add(errorMessage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,25 @@
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
/**
* Handles user registration requests.
*
* Accepts a JSON payload containing user sign-up information, processes the registration, and responds with HTTP 201 Created upon success.
*
* @param signUpDTO the user registration data
* @return HTTP 201 Created with no response body if registration is successful
*/
@PostMapping("/auth/signup")
public ResponseEntity<Void> signup(@Valid @RequestBody SignUpDTO signUpDTO) {
authService.signUp(signUpDTO);
return ResponseEntity.status(HttpStatus.CREATED).build();
}

/**
* Handles a test POST request to verify JWT authentication.
*
* This endpoint can be used to confirm that JWT-based authentication is functioning correctly.
*/
@PostMapping("/auth/jwt/test")
public void test() {
log.info("test");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
private final User user;
/****
* Returns the authorities granted to the user.
*
* @return a collection containing a single authority representing the user's role
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
Expand All @@ -25,11 +30,21 @@ public String getAuthority() {
return collection;
}

/**
* Returns the password of the user.
*
* @return the user's password
*/
@Override
public String getPassword() {
return user.getPassword();
}

/****
* Returns the user's email address to be used as the username for authentication.
*
* @return the user's email
*/
@Override
public String getUsername() {
return user.getEmail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public class LoginDTO {
@NotBlank(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.")
//@Size(min = 8, message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ตœ์†Œ 8์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
private String password;
/**
* Constructs a LoginDTO with the specified email and password.
*
* @param email the user's email address
* @param password the user's password
*/
@Builder
public LoginDTO(String email, String password) {
this.email = email;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public class User {
private LocalDateTime createdAt;
private String role;

/**
* Constructs a new User with the specified name, email, password, and role, setting the creation time to the current date and time.
*
* @param name the user's name
* @param email the user's email address
* @param password the user's password
* @param role the user's role
*/
@Builder
public User(String name, String email, String password, String role) {
this.name = name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

public class AlreadyExistsEmailException extends CustomException {
public static String MESSAGE = "์ด๋ฏธ ๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.";
/****
* Constructs an exception indicating that the email address is already registered.
*/
public AlreadyExistsEmailException() {
super(MESSAGE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
public class InvalidSignInformation extends CustomException {
private static final String MESSAGE = "์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.";

/****
* Constructs an InvalidSignInformation exception with a predefined message indicating incorrect username or password.
*/
public InvalidSignInformation() {
super(MESSAGE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
@Slf4j
public class JWTFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
/**
* Processes incoming HTTP requests to authenticate users based on JWT tokens.
*
* If a valid, non-expired JWT token is present in the "Authorization" header, sets the authentication in the security context for the request. Otherwise, continues the filter chain without authentication.
*
* @param request the HTTP request
* @param response the HTTP response
* @param filterChain the filter chain to continue processing
* @throws ServletException if an error occurs during filtering
* @throws IOException if an I/O error occurs during filtering
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Expand Down
31 changes: 31 additions & 0 deletions backend/src/main/java/org/juniortown/backend/user/jwt/JWTUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,53 @@ public class JWTUtil {

private final SecretKey secretKey;

/**
* Constructs a JWTUtil instance with a secret key for signing and verifying JWT tokens.
*
* @param secret the secret string used to generate the signing key
*/
public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}

/**
* Extracts the "email" claim from a JWT token.
*
* @param token the JWT token string
* @return the email address contained in the token's "email" claim
*/
public String getUsername(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("email", String.class);
}

/**
* Extracts the "role" claim from a JWT token after verifying its signature.
*
* @param token the JWT token string
* @return the value of the "role" claim in the token payload
*/
public String getRole(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
}

/**
* Determines whether the provided JWT token is expired.
*
* @param token the JWT token to check
* @return true if the token's expiration date is before the current time; false otherwise
*/
public Boolean isExpired(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}

/****
* Creates a signed JWT token containing the specified email and role claims, with a set expiration time.
*
* @param email the email to include as the "email" claim
* @param role the role to include as the "role" claim
* @param expiredMs the token's validity duration in milliseconds from the current time
* @return a compact JWT string signed with the configured secret key
*/
public String createJwt(String email, String role, Long expiredMs) {

return Jwts.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final JWTUtil jwtUtil;


/**
* Attempts to authenticate a user by reading login credentials from the HTTP request body.
*
* Parses the request body as JSON to extract the user's email and password, then delegates authentication to the authentication manager.
*
* @param request the HTTP request containing login credentials in JSON format
* @param response the HTTP response
* @return the authenticated Authentication object if credentials are valid
* @throws AuthenticationException if authentication fails or if the request body cannot be parsed as valid JSON
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws
AuthenticationException {
Expand All @@ -44,7 +54,11 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
}
}

//๋กœ๊ทธ์ธ ์„ฑ๊ณต์‹œ ์‹คํ–‰ํ•˜๋Š” ๋ฉ”์†Œ๋“œ (์—ฌ๊ธฐ์„œ JWT๋ฅผ ๋ฐœ๊ธ‰ํ•˜๋ฉด ๋จ)
/**
* Handles actions upon successful user authentication, including JWT token issuance.
*
* Generates a JWT token valid for 24 hours using the authenticated user's username and role, adds it to the response header, and writes a JSON login success message to the response body with HTTP status 200.
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
CustomUserDetails customUserDetails = (CustomUserDetails)authentication.getPrincipal();
Expand Down Expand Up @@ -74,7 +88,11 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
}
}

//๋กœ๊ทธ์ธ ์‹คํŒจ์‹œ ์‹คํ–‰ํ•˜๋Š” ๋ฉ”์†Œ๋“œ
/**
* Handles actions to perform when user authentication fails during login.
*
* Sets the HTTP response status to 401 (Unauthorized) and writes a JSON-formatted failure message to the response body.
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
log.error("Login failed: {}", failed.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,28 @@
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
Optional<User> findByEmailAndPassword(String email, String password);
/****
* Retrieves a user matching the specified email and password.
*
* @param email the user's email address
* @param password the user's password
* @return an Optional containing the user if found, or empty if no match exists
*/
Optional<User> findByEmailAndPassword(String email, String password);

Optional<User> findByEmail(String email);
/****
* Retrieves a user by their email address.
*
* @param email the email address to search for
* @return an Optional containing the user if found, or empty if no user exists with the specified email
*/
Optional<User> findByEmail(String email);

Boolean existsByEmail(String email);
/****
* Checks if a user exists with the specified email address.
*
* @param email the email address to check for existence
* @return true if a user with the given email exists, false otherwise
*/
Boolean existsByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@
import org.juniortown.backend.user.entity.User;

public interface UserRepositoryCustom {
List<User> getUsers();
/****
* Retrieves a list of all User entities.
*
* @return a list containing User objects
*/
List<User> getUsers();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepositoryCustom{
private final JPAQueryFactory jpaQueryFactory;
/****
* Retrieves a list of users.
*
* @return a list of users, or null if not implemented
*/
@Override
public List<User> getUsers() {
return null;
Expand Down
Loading