diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/SigninRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SigninRequest.java similarity index 72% rename from src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/SigninRequest.java rename to src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SigninRequest.java index 0f55df59..9c62ce69 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/SigninRequest.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SigninRequest.java @@ -1,9 +1,9 @@ -package org.ezcode.codetest.application.usermanagement.auth.dto.signin; +package org.ezcode.codetest.application.usermanagement.auth.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,12 +11,15 @@ @Getter @NoArgsConstructor @AllArgsConstructor +@Schema(description = "로그인 요청") public class SigninRequest { + @Schema(description = "사용자 이메일 주소", example = "user@example.com") @NotBlank @Email(message = "이메일은 공백일 수 없습니다") private String email; + @Schema(description = "비밀번호 (영문, 숫자, 특수문자 포함 8~20자)", example = "Aa1234**") @NotBlank(message = "비밀번호는 공백일 수 없습니다.") @Pattern( regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,20}$", diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signup/SignupRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SignupRequest.java similarity index 68% rename from src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signup/SignupRequest.java rename to src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SignupRequest.java index c24a0ca7..50454a95 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signup/SignupRequest.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SignupRequest.java @@ -1,5 +1,6 @@ -package org.ezcode.codetest.application.usermanagement.auth.dto.signup; +package org.ezcode.codetest.application.usermanagement.auth.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; @@ -11,30 +12,39 @@ @Getter @NoArgsConstructor @AllArgsConstructor +@Schema(description = "회원가입 요청") public class SignupRequest { + @Schema(description = "유저 이메일", example = "email@gmail.com") @NotBlank(message = "이메일은 공백일 수 없습니다") @Email private String email; + @Schema(description = "비밀번호 (영문, 숫자, 특수문자 포함 8~20자)", example = "Aa12345**") @NotBlank(message = "비밀번호는 공백일 수 없습니다.") @Pattern( regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,20}$", message = "비밀번호는 영문, 숫자, 특수문자를 포함한 8~20자리여야 합니다.") private String password; + + @Schema(description = "비밀번호 확인", example = "Aa12345**") @NotBlank(message = "비밀번호 확인은 공백일 수 없습니다.") private String passwordConfirm; + + @Schema(description = "사용자 이름 (최대 15자)", example = "홍길동") @NotBlank(message = "사용자명은 반드시 입력되어야합니다.") @Size(max = 15, message = "이름은 15글자 이하로 입력 가능합니다") private String username; + @Schema(description = "사용자 별명 (최대 20자)", example = "다람쥐쳇바퀴에굴러가") @NotBlank(message = "별명은 반드시 입력되어야합니다") @Size(max = 20, message = "별명은 20글자 이하로 입력 가능합니다") private String nickname; //선택적 입력 + @Schema(description = "나이 (선택 입력)", example = "25") private Integer age; } diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/OAuthResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/OAuthResponse.java similarity index 59% rename from src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/OAuthResponse.java rename to src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/OAuthResponse.java index d94944a0..6e89ee59 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/OAuthResponse.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/OAuthResponse.java @@ -1,7 +1,12 @@ -package org.ezcode.codetest.application.usermanagement.auth.dto.signin; +package org.ezcode.codetest.application.usermanagement.auth.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "소셜 로그인 응답") public record OAuthResponse( + @Schema(description = "accessToken") String accessToken, + @Schema(description = "refreshToken") String refreshToken ){ public static OAuthResponse from(String accessToken, String refreshToken) { diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/RefreshTokenResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/RefreshTokenResponse.java new file mode 100644 index 00000000..026b9438 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/RefreshTokenResponse.java @@ -0,0 +1,13 @@ +package org.ezcode.codetest.application.usermanagement.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "refreshToken 응답") +public record RefreshTokenResponse ( + @Schema(description = "생성된 refreshToken") + String token +) { + public static RefreshTokenResponse from(String token) { + return new RefreshTokenResponse(token); + } +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SigninResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SigninResponse.java new file mode 100644 index 00000000..b3fde6b6 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SigninResponse.java @@ -0,0 +1,14 @@ +package org.ezcode.codetest.application.usermanagement.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "로그인 응답") +public record SigninResponse( + @Schema(description = "생성된 accessToken") + String accessToken, + @Schema(description = "생성된 refreshToken") + String refreshToken) { + public static SigninResponse from(String accessToken, String refreshToken) { + return new SigninResponse(accessToken, refreshToken); + } +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SignupResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SignupResponse.java new file mode 100644 index 00000000..bb143477 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SignupResponse.java @@ -0,0 +1,13 @@ +package org.ezcode.codetest.application.usermanagement.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "회원가입 응답") +public record SignupResponse( + @Schema(description = "생성된 token") + String token +) { + public static SignupResponse from(String token) { + return new SignupResponse(token); + } +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/RefreshTokenResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/RefreshTokenResponse.java deleted file mode 100644 index 36d07590..00000000 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/RefreshTokenResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.ezcode.codetest.application.usermanagement.auth.dto.signin; - -public record RefreshTokenResponse (String token) { - public static RefreshTokenResponse from(String token) { - return new RefreshTokenResponse(token); - } -} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/SigninResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/SigninResponse.java deleted file mode 100644 index 1d57c856..00000000 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signin/SigninResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.ezcode.codetest.application.usermanagement.auth.dto.signin; - - -public record SigninResponse(String accessToken, String refreshToken) { - public static SigninResponse from(String accessToken, String refreshToken) { - return new SigninResponse(accessToken, refreshToken); - } -} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signup/SignupResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signup/SignupResponse.java deleted file mode 100644 index 22fe1823..00000000 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/signup/SignupResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.ezcode.codetest.application.usermanagement.auth.dto.signup; - - -public record SignupResponse(String token) { - public static SignupResponse from(String token) { - return new SignupResponse(token); - } -} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/port/JwtUtil.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/port/JwtUtil.java deleted file mode 100644 index 978303c3..00000000 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/port/JwtUtil.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.ezcode.codetest.application.usermanagement.auth.port; - -import org.ezcode.codetest.domain.user.model.enums.Tier; -import org.ezcode.codetest.domain.user.model.enums.UserRole; - -import io.jsonwebtoken.Claims; - -public interface JwtUtil { - - String createToken(Long userId, String email, UserRole userRole, String username, String nickname, Tier tier); - - String substringToken(String token); - - Claims extractClaims(String token); - - Long getExpiration(String token); - - Long getRemainingTime(String token); - - String createRefreshToken(Long userId); - - Long getUserId(String token); - - boolean validateToken(String refreshToken); -} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java index c7579dfe..6d58b426 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java @@ -2,18 +2,18 @@ import java.util.concurrent.TimeUnit; -import org.ezcode.codetest.application.usermanagement.auth.dto.signin.RefreshTokenResponse; -import org.ezcode.codetest.application.usermanagement.auth.dto.signin.SigninRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.signin.SigninResponse; -import org.ezcode.codetest.application.usermanagement.auth.dto.signup.SignupRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.signup.SignupResponse; -import org.ezcode.codetest.application.usermanagement.auth.port.JwtUtil; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.RefreshTokenResponse; +import org.ezcode.codetest.application.usermanagement.auth.dto.request.SigninRequest; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.SigninResponse; +import org.ezcode.codetest.application.usermanagement.auth.dto.request.SignupRequest; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.SignupResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.LogoutResponse; import org.ezcode.codetest.domain.user.exception.AuthException; import org.ezcode.codetest.domain.user.exception.AuthExceptionCode; import org.ezcode.codetest.domain.user.model.entity.User; import org.ezcode.codetest.domain.user.model.enums.AuthType; import org.ezcode.codetest.domain.user.service.UserDomainService; +import org.ezcode.codetest.common.security.util.JwtUtil; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ChangeUserPasswordRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ChangeUserPasswordRequest.java index 38cb62c2..9e53d152 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ChangeUserPasswordRequest.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ChangeUserPasswordRequest.java @@ -1,12 +1,17 @@ package org.ezcode.codetest.application.usermanagement.user.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; +@Schema(description = "비밀번호 변경 요청") public record ChangeUserPasswordRequest( + + @Schema(description = "기존 비밀번호와 일치해야합니다", example = "Aa1234**") @NotBlank String oldPassword, + @Schema(description = "비밀번호 (영문, 숫자, 특수문자 포함 8~20자)", example = "Aa1234**") @NotBlank(message = "비밀번호는 공백일 수 없습니다.") @Pattern( regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,20}$", diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ModifyUserInfoRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ModifyUserInfoRequest.java index 8362d7fc..f02bfcc5 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ModifyUserInfoRequest.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ModifyUserInfoRequest.java @@ -1,16 +1,25 @@ package org.ezcode.codetest.application.usermanagement.user.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "회원 정보 변경 요청") public record ModifyUserInfoRequest( + @Schema(description = "변경할 별명", example = "코딩짱") String nickname, + @Schema(description = "GitHub 주소", example = "https://github.com/username") String githubUrl, + @Schema(description = "블로그 주소", example = "https://blog.example.com") String blogUrl, + @Schema(description = "프로필 이미지 URL", example = "https://cdn.example.com/images/profile.jpg") String profileImageUrl, + @Schema(description = "자기소개", example = "안녕하세요, 백엔드 개발자입니다.") String introduction, + @Schema(description = "나이", example = "28") Integer age ) { } diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/ChangeUserPasswordResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/ChangeUserPasswordResponse.java index 878de7cb..c460ef00 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/ChangeUserPasswordResponse.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/ChangeUserPasswordResponse.java @@ -1,9 +1,12 @@ package org.ezcode.codetest.application.usermanagement.user.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter +@Schema(description = "비밀번호 변경 응답 DTO") public class ChangeUserPasswordResponse { + @Schema(description = "응답 메시지", example = "비밀번호가 성공적으로 변경되었습니다.") String message; public ChangeUserPasswordResponse(String message) { this.message = message; diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GoogleOAuth2Response.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GoogleOAuth2Response.java index b21f0566..6cc6884f 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GoogleOAuth2Response.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GoogleOAuth2Response.java @@ -2,6 +2,9 @@ import java.util.Map; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "Google OAuth2 응답 처리 클래스") public class GoogleOAuth2Response implements OAuth2Response { private final Map attributes; @@ -11,22 +14,26 @@ public GoogleOAuth2Response(Map attributes) { } @Override + @Schema(description = "OAuth 제공자 이름", example = "google") public String getProvider() { return "google"; } @Override + @Schema(description = "제공자 내부 식별자", example = "109000123456789012345") public String getProviderId() { //구글에서는 "sub"으로 제공함 return attributes.get("sub").toString(); } @Override + @Schema(description = "사용자 이메일", example = "user@gmail.com") public String getEmail() { return attributes.get("email").toString(); } @Override + @Schema(description = "사용자 이름", example = "홍길동") public String getName() { return attributes.get("name").toString(); } diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/LogoutResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/LogoutResponse.java index a894bccb..bff6dd4b 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/LogoutResponse.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/LogoutResponse.java @@ -1,9 +1,12 @@ package org.ezcode.codetest.application.usermanagement.user.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter +@Schema(description = "로그아웃 응답 DTO") public class LogoutResponse { + @Schema(description = "로그아웃 메시지", example = "성공적으로 로그아웃되었습니다.") String message; public LogoutResponse(String message) { diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/SimpleUserInfoResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/SimpleUserInfoResponse.java index 4ede57a4..316a1c2d 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/SimpleUserInfoResponse.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/SimpleUserInfoResponse.java @@ -3,14 +3,23 @@ import org.ezcode.codetest.domain.user.model.entity.User; import org.ezcode.codetest.domain.user.model.enums.Tier; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter +@Schema(description = "간단한 사용자 정보 응답 DTO") public class SimpleUserInfoResponse { + @Schema(description = "사용자 ID", example = "1") private final Long userId; + + @Schema(description = "사용자 별명", example = "다람쥐쳇바퀴에굴러가") private final String nickname; + + @Schema(description = "사용자 티어", example = "GOLD") private final Tier tier; + + @Schema(description = "프로필 이미지 URL", example = "https://cdn.example.com/profile.jpg") private final String profileImageUrl; @Builder diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserInfoResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserInfoResponse.java index 3154dc00..4e2e442b 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserInfoResponse.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserInfoResponse.java @@ -4,20 +4,41 @@ import org.ezcode.codetest.domain.user.model.enums.Tier; import org.ezcode.codetest.domain.user.model.enums.UserRole; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter +@Schema(description = "회원 정보 응답 DTO") public class UserInfoResponse { + @Schema(description = "사용자 이름", example = "홍길동") private final String username; + + @Schema(description = "사용자 이메일", example = "user@example.com") private final String email; + + @Schema(description = "나이", example = "25") private final Integer age; + + @Schema(description = "닉네임", example = "길동이짱") private final String nickname; + + @Schema(description = "사용자 역할", example = "USER") private final UserRole userRole; + + @Schema(description = "GitHub URL", example = "https://github.com/example") private final String githubUrl; + + @Schema(description = "블로그 URL", example = "https://blog.example.com") private final String blogUrl; + + @Schema(description = "프로필 이미지 URL", example = "https://cdn.example.com/image.jpg") private final String profileImageUrl; + + @Schema(description = "자기소개", example = "안녕하세요, 백엔드 개발자입니다.") private final String introduction; + + @Schema(description = "사용자 티어", example = "GOLD") private final Tier tier; @Builder diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/WithdrawUserResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/WithdrawUserResponse.java index 27eeffa2..193b001a 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/WithdrawUserResponse.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/WithdrawUserResponse.java @@ -1,4 +1,9 @@ package org.ezcode.codetest.application.usermanagement.user.dto.response; -public record WithdrawUserResponse(String message) { -} +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "회원 탈퇴 응답 DTO") +public record WithdrawUserResponse( + @Schema(description = "응답 메시지", example = "회원 탈퇴가 완료되었습니다.") + String message +) {} diff --git a/src/main/java/org/ezcode/codetest/common/annotation/Auth.java b/src/main/java/org/ezcode/codetest/common/annotation/Auth.java deleted file mode 100644 index 0de422db..00000000 --- a/src/main/java/org/ezcode/codetest/common/annotation/Auth.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ezcode.codetest.common.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.PARAMETER, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Auth { -} diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/config/SecurityConfig.java b/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java similarity index 80% rename from src/main/java/org/ezcode/codetest/infrastructure/security/config/SecurityConfig.java rename to src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java index 4924d395..6261519d 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/config/SecurityConfig.java +++ b/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java @@ -1,10 +1,11 @@ -package org.ezcode.codetest.infrastructure.security.config; +package org.ezcode.codetest.common.security.config; import org.ezcode.codetest.domain.user.service.CustomOAuth2UserService; -import org.ezcode.codetest.infrastructure.security.hander.CustomSuccessHandler; -import org.ezcode.codetest.infrastructure.security.jwt.ExceptionHandlingFilter; -import org.ezcode.codetest.infrastructure.security.jwt.JwtFilter; -import org.ezcode.codetest.infrastructure.security.jwt.JwtUtilImpl; +import org.ezcode.codetest.common.security.hander.CustomSuccessHandler; +import org.ezcode.codetest.common.security.util.ExceptionHandlingFilter; +import org.ezcode.codetest.common.security.util.JwtFilter; +import org.ezcode.codetest.common.security.util.JwtUtil; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; @@ -23,7 +24,7 @@ @RequiredArgsConstructor @EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { - private final JwtUtilImpl jwtUtil; + private final JwtUtil jwtUtil; private final RedisTemplate redisTemplate; private final CustomOAuth2UserService customOAuth2UserService; //OAuth2.0 서비스 private final CustomSuccessHandler customSuccessHandler; @@ -52,10 +53,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers( - "/", - "/signin", + "/auth/**", + "/refresh", "/signup", - "/logout", "/login", "/ezlogin", "/login/**", @@ -83,4 +83,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .build(); } + + @Bean + public FilterRegistrationBean jwtFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new JwtFilter(jwtUtil, redisTemplate)); + registrationBean.addUrlPatterns("/*"); + + return registrationBean; + } } diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/hander/CustomSuccessHandler.java b/src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java similarity index 87% rename from src/main/java/org/ezcode/codetest/infrastructure/security/hander/CustomSuccessHandler.java rename to src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java index 1325a5b4..103e30a3 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/hander/CustomSuccessHandler.java +++ b/src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java @@ -1,14 +1,13 @@ -package org.ezcode.codetest.infrastructure.security.hander; +package org.ezcode.codetest.common.security.hander; import java.io.IOException; import java.util.concurrent.TimeUnit; -import org.ezcode.codetest.application.usermanagement.auth.dto.signin.OAuthResponse; -import org.ezcode.codetest.application.usermanagement.auth.port.JwtUtil; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.OAuthResponse; import org.ezcode.codetest.domain.user.model.entity.CustomOAuth2User; import org.ezcode.codetest.domain.user.model.entity.User; import org.ezcode.codetest.domain.user.service.UserDomainService; -import org.ezcode.codetest.infrastructure.security.jwt.JwtUtilImpl; +import org.ezcode.codetest.common.security.util.JwtUtil; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @@ -29,7 +28,7 @@ public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler private final RedisTemplate redisTemplate; private final ObjectMapper objectMapper; //json직렬화 - public CustomSuccessHandler(JwtUtilImpl jwtUtil, UserDomainService userDomainService, + public CustomSuccessHandler(JwtUtil jwtUtil, UserDomainService userDomainService, RedisTemplate redisTemplate, ObjectMapper objectMapper) { this.jwtUtil = jwtUtil; this.userDomainService = userDomainService; diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/.gitkeep b/src/main/java/org/ezcode/codetest/common/security/util/.gitkeep similarity index 100% rename from src/main/java/org/ezcode/codetest/infrastructure/security/jwt/.gitkeep rename to src/main/java/org/ezcode/codetest/common/security/util/.gitkeep diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/ExceptionHandlingFilter.java b/src/main/java/org/ezcode/codetest/common/security/util/ExceptionHandlingFilter.java similarity index 96% rename from src/main/java/org/ezcode/codetest/infrastructure/security/jwt/ExceptionHandlingFilter.java rename to src/main/java/org/ezcode/codetest/common/security/util/ExceptionHandlingFilter.java index 27a85325..2fddf529 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/ExceptionHandlingFilter.java +++ b/src/main/java/org/ezcode/codetest/common/security/util/ExceptionHandlingFilter.java @@ -1,4 +1,4 @@ -package org.ezcode.codetest.infrastructure.security.jwt; +package org.ezcode.codetest.common.security.util; import java.io.IOException; diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/JwtFilter.java b/src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java similarity index 93% rename from src/main/java/org/ezcode/codetest/infrastructure/security/jwt/JwtFilter.java rename to src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java index 99b26573..218de9be 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/JwtFilter.java +++ b/src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java @@ -1,4 +1,4 @@ -package org.ezcode.codetest.infrastructure.security.jwt; +package org.ezcode.codetest.common.security.util; import java.io.IOException; import java.util.Collection; @@ -28,7 +28,7 @@ @Slf4j public class JwtFilter extends OncePerRequestFilter { - private final JwtUtilImpl jwtUtilImpl; + private final JwtUtil jwtUtil; private final RedisTemplate redisTemplate; @Override @@ -42,13 +42,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - String jwt = jwtUtilImpl.substringToken(bearerToken); + String jwt = jwtUtil.substringToken(bearerToken); if (redisTemplate.opsForValue().get("LOGOUT:" + jwt) != null) { throw new AuthException(AuthExceptionCode.LOGOUT_USER); } - Claims claims = jwtUtilImpl.extractClaims(jwt); + Claims claims = jwtUtil.extractClaims(jwt); if (claims == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "잘못된 JWT 토큰입니다"); diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/JwtUtilImpl.java b/src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java similarity index 94% rename from src/main/java/org/ezcode/codetest/infrastructure/security/jwt/JwtUtilImpl.java rename to src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java index 03b44e69..36aa9574 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/JwtUtilImpl.java +++ b/src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java @@ -1,4 +1,4 @@ -package org.ezcode.codetest.infrastructure.security.jwt; +package org.ezcode.codetest.common.security.util; import java.security.Key; import java.util.Date; @@ -7,8 +7,6 @@ import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; - -import org.ezcode.codetest.application.usermanagement.auth.port.JwtUtil; import org.ezcode.codetest.common.exception.ServerException; import org.ezcode.codetest.domain.user.model.enums.Tier; import org.ezcode.codetest.domain.user.model.enums.UserRole; @@ -22,7 +20,7 @@ @Slf4j(topic = "JwtUtil") @Component -public class JwtUtilImpl implements JwtUtil { +public class JwtUtil { private static final String BEARER_PREFIX = "Bearer "; private static final long TOKEN_EXPIRATION_TIME = 60 * 60 * 24 * 7; @@ -95,7 +93,6 @@ public Long getExpiration(String token) { return expiration.getTime() - System.currentTimeMillis(); // 남은시간을 ms 단위로 계산해서 반환 } - @Override public Long getRemainingTime(String token) { Date expiration = extractClaims(token).getExpiration(); return expiration.getTime() - System.currentTimeMillis(); @@ -104,7 +101,6 @@ public Long getRemainingTime(String token) { /* 토큰 갱신 */ - @Override public String createRefreshToken(Long userId) { Date now = new Date(); Date expirationDate = new Date(now.getTime() + TOKEN_EXPIRATION_TIME * 1000L); //만료 시간 @@ -117,7 +113,6 @@ public String createRefreshToken(Long userId) { .compact(); } - @Override public Long getUserId(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(key) @@ -128,7 +123,6 @@ public Long getUserId(String token) { return Long.parseLong(claims.getSubject()); } - @Override public boolean validateToken(String refreshToken) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(refreshToken); diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/PasswordEncoder.java b/src/main/java/org/ezcode/codetest/common/security/util/PasswordEncoder.java similarity index 89% rename from src/main/java/org/ezcode/codetest/infrastructure/security/jwt/PasswordEncoder.java rename to src/main/java/org/ezcode/codetest/common/security/util/PasswordEncoder.java index 89bd12e5..74e1fb58 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/jwt/PasswordEncoder.java +++ b/src/main/java/org/ezcode/codetest/common/security/util/PasswordEncoder.java @@ -1,4 +1,4 @@ -package org.ezcode.codetest.infrastructure.security.jwt; +package org.ezcode.codetest.common.security.util; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java b/src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java index 8ed5e11f..b462b31e 100644 --- a/src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java +++ b/src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java @@ -4,7 +4,7 @@ import org.ezcode.codetest.domain.user.exception.AuthExceptionCode; import org.ezcode.codetest.domain.user.model.entity.User; import org.ezcode.codetest.domain.user.repository.UserRepository; -import org.ezcode.codetest.infrastructure.security.jwt.PasswordEncoder; +import org.ezcode.codetest.common.security.util.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/org/ezcode/codetest/infrastructure/event/config/CustomHandShakeHandler.java b/src/main/java/org/ezcode/codetest/infrastructure/event/config/CustomHandShakeHandler.java index 87b31270..c90ba817 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/event/config/CustomHandShakeHandler.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/event/config/CustomHandShakeHandler.java @@ -4,7 +4,7 @@ import java.security.Principal; import java.util.Map; -import org.ezcode.codetest.infrastructure.security.jwt.JwtUtilImpl; +import org.ezcode.codetest.common.security.util.JwtUtil; import org.springframework.http.server.ServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; @@ -16,7 +16,7 @@ @RequiredArgsConstructor public class CustomHandShakeHandler extends DefaultHandshakeHandler { - private final JwtUtilImpl jwtUtilImpl; + private final JwtUtil jwtUtil; @Override protected Principal determineUser( @@ -33,7 +33,7 @@ protected Principal determineUser( } //TODO : 토큰의 대한 예외처리 아직 구현 x - Claims claims = jwtUtilImpl.extractClaims(tokenParam); + Claims claims = jwtUtil.extractClaims(tokenParam); String email = claims.get("email", String.class); return () -> email; diff --git a/src/main/java/org/ezcode/codetest/infrastructure/event/config/WebSocketConfig.java b/src/main/java/org/ezcode/codetest/infrastructure/event/config/WebSocketConfig.java index 63e88231..0cb98683 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/event/config/WebSocketConfig.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/event/config/WebSocketConfig.java @@ -1,6 +1,6 @@ package org.ezcode.codetest.infrastructure.event.config; -import org.ezcode.codetest.infrastructure.security.jwt.JwtUtilImpl; +import org.ezcode.codetest.common.security.util.JwtUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; @@ -15,7 +15,7 @@ @RequiredArgsConstructor public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - private final JwtUtilImpl jwtUtil; + private final JwtUtil jwtUtil; @Value("${spring.message.activemq.address}") private String mqAddress; diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/config/FilterConfig.java b/src/main/java/org/ezcode/codetest/infrastructure/security/config/FilterConfig.java deleted file mode 100644 index 73ff3b87..00000000 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/config/FilterConfig.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.ezcode.codetest.infrastructure.security.config; - -import org.ezcode.codetest.infrastructure.security.jwt.JwtFilter; -import org.ezcode.codetest.infrastructure.security.jwt.JwtUtilImpl; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.core.RedisTemplate; - -import lombok.RequiredArgsConstructor; - -@Configuration -@RequiredArgsConstructor -public class FilterConfig { - private final JwtUtilImpl jwtUtil; - private final RedisTemplate redisTemplate; - - @Bean - public FilterRegistrationBean jwtFilter() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); - registrationBean.setFilter(new JwtFilter(jwtUtil, redisTemplate)); - registrationBean.addUrlPatterns("/*"); - - return registrationBean; - } -} diff --git a/src/main/java/org/ezcode/codetest/infrastructure/security/config/WebConfig.java b/src/main/java/org/ezcode/codetest/infrastructure/security/config/WebConfig.java deleted file mode 100644 index 36cc028f..00000000 --- a/src/main/java/org/ezcode/codetest/infrastructure/security/config/WebConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.ezcode.codetest.infrastructure.security.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Configuration -@RequiredArgsConstructor -public class WebConfig implements WebMvcConfigurer { - -} diff --git a/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java b/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java index d4ad7e11..e8bcb9de 100644 --- a/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java +++ b/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java @@ -1,10 +1,10 @@ package org.ezcode.codetest.presentation.usermanagement; -import org.ezcode.codetest.application.usermanagement.auth.dto.signin.RefreshTokenResponse; -import org.ezcode.codetest.application.usermanagement.auth.dto.signin.SigninRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.signin.SigninResponse; -import org.ezcode.codetest.application.usermanagement.auth.dto.signup.SignupRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.signup.SignupResponse; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.RefreshTokenResponse; +import org.ezcode.codetest.application.usermanagement.auth.dto.request.SigninRequest; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.SigninResponse; +import org.ezcode.codetest.application.usermanagement.auth.dto.request.SignupRequest; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.SignupResponse; import org.ezcode.codetest.application.usermanagement.auth.service.AuthService; import org.ezcode.codetest.application.usermanagement.user.dto.response.LogoutResponse; import org.ezcode.codetest.domain.user.model.entity.AuthUser; @@ -15,6 +15,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -23,27 +25,32 @@ @Slf4j @RestController @RequiredArgsConstructor +@Tag(name = "인증/인가", description = "회원가입, 로그인, 로그아웃, 토큰 재발급 관련 API") public class AuthController { private final AuthService authService; - @PostMapping("/signup") + @Operation(summary = "회원가입", description = "이메일, 비밀번호 등 정보를 입력받아 회원가입을 진행합니다.") + @PostMapping("/auth/signup") public ResponseEntity signup(@Valid @RequestBody SignupRequest signupRequest) { return ResponseEntity.status(HttpStatus.CREATED).body(authService.signup(signupRequest)); } - @PostMapping("/signin") + @Operation(summary = "로그인", description = "이메일과 비밀번호로 로그인하고 토큰을 발급받습니다.") + @PostMapping("/auth/signin") public ResponseEntity signin(@Valid @RequestBody SigninRequest signinRequest) { return ResponseEntity.status(HttpStatus.OK).body(authService.signin(signinRequest)); } - @PostMapping("/logout") + @Operation(summary = "로그아웃", description = "현재 로그인된 사용자의 로그아웃을 수행합니다.") + @PostMapping("/auth/logout") public ResponseEntity logout( @AuthenticationPrincipal AuthUser authUser, HttpServletRequest request) { return ResponseEntity.status(HttpStatus.OK).body(authService.logout(authUser.getId(), request)); } - @PostMapping("/refresh") + @Operation(summary = "토큰 재발급", description = "리프레시 토큰을 이용하여 새로운 액세스 토큰을 발급합니다.") + @PostMapping("/auth/refresh") public ResponseEntity refresh(HttpServletRequest request) { return ResponseEntity.status(HttpStatus.OK).body(authService.refreshToken(request)); } diff --git a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java index 7f64555a..95fa3201 100644 --- a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java +++ b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java @@ -16,6 +16,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,15 +25,18 @@ @Slf4j @RestController @RequiredArgsConstructor +@Tag(name = "사용자 기본 기능", description = "사용자 정보 조회, 수정, 비밀번호 변경, 회원 탈퇴 API") public class UserController { private final UserService userService; + @Operation(summary = "내 정보 조회", description = "로그인한 사용자의 정보를 조회합니다.") @GetMapping("/users") public ResponseEntity getUserInfo(@AuthenticationPrincipal AuthUser authUser){ log.info("authUserEmail: {}, authUserID : {}", authUser.getEmail(), authUser.getId()); return ResponseEntity.status(HttpStatus.OK).body(userService.getUserInfo(authUser)); } + @Operation(summary = "내 정보 수정", description = "닉네임, 블로그, 깃허브, 소개 등 개인 정보를 추가하거나 수정합니다.") @PutMapping("/users") public ResponseEntity modifyUserInfo( @AuthenticationPrincipal AuthUser authUser, @@ -40,6 +45,7 @@ public ResponseEntity modifyUserInfo( return ResponseEntity.status(HttpStatus.OK).body(userService.modifyUserInfo(authUser, modifyUserInfoRequest)); } + @Operation(summary = "비밀번호 변경", description = "기존 비밀번호와 새 비밀번호를 입력하여 비밀번호를 변경합니다.") @PutMapping("/users/password") public ResponseEntity modifyUserPassword( @AuthenticationPrincipal AuthUser authUser, @@ -48,6 +54,8 @@ public ResponseEntity modifyUserPassword( return ResponseEntity.status(HttpStatus.OK).body(userService.modifyUserPassword(authUser, changeUserPasswordRequest)); } + + @Operation(summary = "회원 탈퇴", description = "현재 로그인된 사용자를 탈퇴 처리합니다.") @DeleteMapping("/users/withdraw") public ResponseEntity withdraw( @AuthenticationPrincipal AuthUser authUser