Skip to content
Merged
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
38 changes: 27 additions & 11 deletions src/main/java/com/blog/domain/auth/controller/OAuth2Controller.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.blog.domain.auth.controller;

import com.blog.domain.auth.dto.ResponseMessage;
import com.blog.domain.auth.dto.requests.OAuthRegisterRequest;
import com.blog.domain.auth.dto.responses.LoginPostResponse;
import com.blog.domain.auth.dto.responses.OAuthLoginResponse;
import com.blog.domain.auth.dto.responses.RegisterPostResponse;
import com.blog.domain.auth.entity.enums.OAuthProvider;
import com.blog.domain.auth.service.OAuth2Service;
import com.blog.global.common.dto.ResponseDto;
Expand All @@ -10,11 +13,14 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -31,36 +37,46 @@ public class OAuth2Controller {
// utils
private final OAuth2AuthExecutor oAuth2AuthExecutor;

@GetMapping("/{provider}")
@Operation(summary = "OAuth2 로그인을 위한 redirect URL을 반환합니다. provider에는 'kakao'가 들어갑니다.")
@GetMapping("/kakao")
@Operation(summary = "OAuth2 로그인을 위한 redirect URL을 반환합니다.")
public void getOAuthLogin(
@PathVariable("provider") String provider,
HttpServletResponse httpServletResponse
) throws IOException {
OAuthProvider oAuthProviders = OAuthProvider.valueOf(provider.toUpperCase());
OAuthProvider oAuthProviders = OAuthProvider.valueOf("KAKAO");
String authorizationUrl = this.oAuth2AuthExecutor.getAuthorizationUrl(
oAuthProviders.getOauthUrlBuilderClass());
httpServletResponse.sendRedirect(authorizationUrl);
}

@GetMapping("/{provider}/redirect")
@Operation(summary = "AuthCode를 받아 OAuth2 로그인을 진행합니다. provider에는 'kakao'가 들어갑니다.")
public ResponseDto<LoginPostResponse> getOAuthLoginRedirect(
@PathVariable("provider") String provider,
@GetMapping("/kakao/redirect")
@Operation(summary = "AuthCode를 받아 OAuth2 로그인을 진행합니다.")
public ResponseDto<OAuthLoginResponse> getOAuthLoginRedirect(
@RequestParam("code") String code) {
OAuthProvider oAuthProviders = OAuthProvider.valueOf(provider.toUpperCase());
OAuthProvider oAuthProviders = OAuthProvider.valueOf("KAKAO");
MemberInfoFromProviders memberInfoFromProviders =
this.oAuth2AuthExecutor.getMemberInfoFrom(
oAuthProviders.getOauth2ProviderClientClass(),
code
);

LoginPostResponse loginInfo = this.oAuth2Service.oauth2Login(memberInfoFromProviders);
OAuthLoginResponse loginInfo = this.oAuth2Service.oauth2Login(memberInfoFromProviders);

return ResponseDto.of(
HttpStatus.OK.value(),
ResponseMessage.LOGIN_SUCCESS.getMessage(),
loginInfo.getResponseMessage(),
loginInfo
);
}

@PostMapping("/register-oauth")
@Operation(summary = "OAuth2 회원가입을 진행합니다.")
public ResponseDto<RegisterPostResponse> postOAuthRegister(
@RequestBody @Valid OAuthRegisterRequest oAuthRegisterRequest
) {
return ResponseDto.of(
HttpStatus.CREATED.value(),
ResponseMessage.REGISTER_SUCCESS.getMessage(),
this.oAuth2Service.oauth2Register(oAuthRegisterRequest)
);
}
}
3 changes: 2 additions & 1 deletion src/main/java/com/blog/domain/auth/dto/ResponseMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public enum ResponseMessage {
NOT_FOUND_ACCESS_TOKEN("Access Token을 찾을 수 없습니다."),
NOT_FOUND_REFRESH_TOKEN("Refresh Token을 찾을 수 없습니다."),
REISSUE_ACCESS_TOKEN_SUCCESS("액세스 토큰 재발급에 성공했습니다."),
OAUTH2_REGISTER_FAILURE("OAuth2 회원가입에 실패했습니다.");
OAUTH2_REGISTER_FAILURE("OAuth2 회원가입에 실패했습니다."),
OAUTH2_REGISTER_REQUIRED("OAuth2 회원가입이 필요합니다.");

private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.blog.domain.auth.dto.requests;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public record OAuthRegisterRequest(
@Schema(description = "사용자 이메일", example = "[email protected]")
@NotEmpty(message = "이메일은 필수 입력 사항입니다.")
@Email(regexp = "^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(\\.\\w{2,3})+$", message = "이메일 형식이 올바르지 않습니다.")
String email,

@Schema(description = "사용자 닉네임 (영문, 숫자만 가능)", example = "john123")
@NotEmpty(message = "닉네임은 공백이 아니어야 합니다.")
@Size(min = 3, max = 20, message = "닉네임은 최소 3자 이상, 최대 20자 이하여야 합니다.")
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "닉네임은 영문, 숫자만 가능합니다.")
String nickname,

@Schema(description = "프로필 사진 URL", example = "https://example.com/profile.jpg")
@Size(max = 1000, message = "프로필 사진의 길이가, 허용량을 초과했습니다.")
String profilePicture,

@Schema(description = "생년월일 (YYYY-MM-DD)", example = "2000-01-01")
@NotNull(message = "생년월일은 필수 입력 사항입니다.")
String birthDate,

@Schema(description = "사용자 이름", example = "김주영")
@Size(max = 20, message = "이름은 최대 20자 이하여야 합니다.")
@Pattern(regexp = "^[가-힣]*$", message = "이름은 한글만 가능합니다.")
@NotEmpty(message = "이름은 공백이 아니어야 합니다.")
String name,

@Schema(description = "사용자 소개", example = "안녕하세요!")
@Size(max = 30, message = "소개는 최대 30자 이하여야 합니다.")
String introduction
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,20 @@ public record RegisterPostRequest(

@Schema(description = "프로필 사진 URL", example = "https://example.com/profile.jpg")
@Size(max = 1000, message = "프로필 사진의 길이가, 허용량을 초과했습니다.")
String profilePicture
) {
String profilePicture,

@Schema(description = "생년월일 (YYYY-MM-DD)", example = "2000-01-01")
@NotNull(message = "생년월일은 필수 입력 사항입니다.")
String birthDate,

public static RegisterPostRequest createOauthRegister(String email, String nickname, String password, String profilePicture) {
if (email == null) {
email = UUID.randomUUID().toString().replace("-", "") + "@leets.land";
}
return new RegisterPostRequest(email, nickname, password, profilePicture);
}
@Schema(description = "사용자 이름", example = "김주영")
@Size(max = 20, message = "이름은 최대 20자 이하여야 합니다.")
@Pattern(regexp = "^[가-힣]*$", message = "이름은 한글만 가능합니다.")
@NotEmpty(message = "이름은 공백이 아니어야 합니다.")
String name,

@Schema(description = "사용자 소개", example = "안녕하세요!")
@Size(max = 30, message = "소개는 최대 30자 이하여야 합니다.")
String introduction
) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.blog.domain.auth.dto.responses;

import com.blog.domain.auth.dto.ResponseMessage;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "로그인 응답 DTO")
Expand All @@ -15,5 +16,10 @@ public record LoginPostResponse(

@Schema(description = "프로필 사진 URL", example = "https://example.com/profile.jpg")
String profilePicture
) {
) implements OAuthLoginResponse{

@Override
public String getResponseMessage() {
return ResponseMessage.LOGIN_SUCCESS.getMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.blog.domain.auth.dto.responses;

public interface OAuthLoginResponse {

String getResponseMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.blog.domain.auth.dto.responses;

import com.blog.domain.auth.dto.ResponseMessage;
import com.blog.global.common.oauth.MemberInfoFromProviders;

public record OAuthRegisterRequiredResponse(
String nickname,
String picture
) implements OAuthLoginResponse{

@Override
public String getResponseMessage() {
return ResponseMessage.OAUTH2_REGISTER_REQUIRED.getMessage();
}

public static OAuthRegisterRequiredResponse from(MemberInfoFromProviders memberInfoFromProviders) {
return new OAuthRegisterRequiredResponse(
memberInfoFromProviders.nickname(),
memberInfoFromProviders.picture()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.blog.domain.auth.dto.responses;

import com.blog.domain.user.domain.entity.User;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "회원가입 응답 DTO")
Expand All @@ -13,4 +14,12 @@ public record RegisterPostResponse(
@Schema(description = "등록된 프로필 사진 URL", example = "https://example.com/profile.jpg")
String profilePicture
) {

public static RegisterPostResponse of(User user) {
return new RegisterPostResponse(
user.getEmail(),
user.getNickname(),
user.getProfilePicture()
);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/blog/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.blog.domain.user.domain.entity.User;
import com.blog.domain.user.domain.service.UserService;
import com.blog.domain.user.exception.EmailDuplicateException;
import com.blog.domain.user.exception.NicknameDuplicateException;
import com.blog.global.common.utils.jwt.JwtAuthenticator;
import com.blog.global.common.utils.jwt.JwtExtractor;
import com.blog.global.common.utils.jwt.JwtProvider;
Expand Down Expand Up @@ -74,6 +75,11 @@ public RegisterPostResponse register(RegisterPostRequest registerPostRequest) {
throw new EmailDuplicateException();
}

boolean isNicknameDuplicate = this.userService.checkNicknameDuplicate(registerPostRequest.nickname());
if (isNicknameDuplicate) {
throw new NicknameDuplicateException();
}

String hashedPassword = this.userService.hashPassword(registerPostRequest.password());

User user = User.create(registerPostRequest, hashedPassword);
Expand Down
42 changes: 26 additions & 16 deletions src/main/java/com/blog/domain/auth/service/OAuth2Service.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.blog.domain.auth.service;

import com.blog.domain.auth.dto.requests.LoginPostRequest;
import com.blog.domain.auth.dto.requests.RegisterPostRequest;
import com.blog.domain.auth.dto.responses.LoginPostResponse;
import com.blog.domain.auth.dto.requests.OAuthRegisterRequest;
import com.blog.domain.auth.dto.responses.OAuthLoginResponse;
import com.blog.domain.auth.dto.responses.OAuthRegisterRequiredResponse;
import com.blog.domain.auth.dto.responses.RegisterPostResponse;
import com.blog.domain.auth.exception.OAuth2RegisterFailureException;
import com.blog.domain.user.domain.entity.User;
import com.blog.domain.user.domain.service.UserService;
import com.blog.domain.user.exception.EmailDuplicateException;
import com.blog.domain.user.exception.NicknameDuplicateException;
import com.blog.global.common.oauth.MemberInfoFromProviders;
import com.blog.global.config.properties.AppConfigProperties;
import jakarta.transaction.Transactional;
Expand All @@ -25,28 +27,36 @@ public class OAuth2Service {
private final AppConfigProperties appConfigProperties;

@Transactional
public LoginPostResponse oauth2Login(MemberInfoFromProviders memberInfoFromProviders) {
public OAuthLoginResponse oauth2Login(MemberInfoFromProviders memberInfoFromProviders) {
Optional<User> getLoginAvailableResponse =
this.userService.checkLoginAvailableByNickname(memberInfoFromProviders.nickname(),
this.appConfigProperties.getOauthDummyPassword());

if (getLoginAvailableResponse.isEmpty()) {
RegisterPostRequest oauthRegister =
RegisterPostRequest.createOauthRegister(
memberInfoFromProviders.email(),
memberInfoFromProviders.nickname(),
this.appConfigProperties.getOauthDummyPassword(),
memberInfoFromProviders.picture()
);
this.authService.register(oauthRegister);

this.userService.checkLoginAvailable(oauthRegister.email(),
this.appConfigProperties.getOauthDummyPassword())
.orElseThrow(OAuth2RegisterFailureException::new);
return OAuthRegisterRequiredResponse.from(memberInfoFromProviders);
}

LoginPostRequest loginPostRequest = LoginPostRequest.createOAuthLogin(
memberInfoFromProviders.nickname(), this.appConfigProperties.getOauthDummyPassword());
return this.authService.login(loginPostRequest, true);
}

@Transactional
public RegisterPostResponse oauth2Register(OAuthRegisterRequest oAuthRegisterRequest) {
boolean isEmailDuplicate = this.userService.checkEmailDuplicate(oAuthRegisterRequest.email());
if (isEmailDuplicate) {
throw new EmailDuplicateException();
}

boolean isNicknameDuplicate = this.userService.checkNicknameDuplicate(oAuthRegisterRequest.nickname());
if (isNicknameDuplicate) {
throw new NicknameDuplicateException();
}

User user = User.create(oAuthRegisterRequest,
this.userService.hashPassword(this.appConfigProperties.getOauthDummyPassword())
);

return RegisterPostResponse.of(this.userService.save(user));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.blog.domain.user.application.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public record UserPatchRequest(
@Schema(description = "사용자 이메일", example = "[email protected]")
@NotEmpty(message = "이메일은 필수 입력 사항입니다.")
@Email(regexp = "^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(\\.\\w{2,3})+$", message = "이메일 형식이 올바르지 않습니다.")
String email,

@Schema(description = "사용자 닉네임 (영문, 숫자만 가능)", example = "john123")
@NotEmpty(message = "닉네임은 공백이 아니어야 합니다.")
@Size(min = 3, max = 20, message = "닉네임은 최소 3자 이상, 최대 20자 이하여야 합니다.")
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "닉네임은 영문, 숫자만 가능합니다.")
String nickname,

@Schema(description = "비밀번호 (8~64자, 영문, 숫자, 특수문자 필수)", example = "Password123!")
@NotEmpty(message = "비밀번호는 공백이 아니어야 합니다.")
@Size(min = 8, max = 64, message = "비밀번호는 최소 8자 이상, 최대 64자 이하여야 합니다.")
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,64}$",
message = "비밀번호 8~64자, 영문, 숫자, 특수문자가 필수입니다.")
String password,

@Schema(description = "프로필 사진 URL", example = "https://example.com/profile.jpg")
@Size(max = 1000, message = "프로필 사진의 길이가, 허용량을 초과했습니다.")
String profilePicture,

@Schema(description = "생년월일 (YYYY-MM-DD)", example = "2000-01-01")
@NotNull(message = "생년월일은 필수 입력 사항입니다.")
String birthDate,

@Schema(description = "사용자 이름", example = "김주영")
@Size(max = 20, message = "이름은 최대 20자 이하여야 합니다.")
@Pattern(regexp = "^[가-힣]*$", message = "이름은 한글만 가능합니다.")
@NotEmpty(message = "이름은 공백이 아니어야 합니다.")
String name,

@Schema(description = "사용자 소개", example = "안녕하세요!")
@Size(max = 30, message = "소개는 최대 30자 이하여야 합니다.")
String introduction
) {

}
Loading