Skip to content

Commit

Permalink
Merge branch 'dev/be' into refactor/767-template-controller-test-mock
Browse files Browse the repository at this point in the history
  • Loading branch information
jminkkk authored Oct 11, 2024
2 parents fbc7e55 + c5cf32f commit a5ab6e9
Show file tree
Hide file tree
Showing 70 changed files with 581 additions and 265 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public Member resolveArgument(
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
AuthenticationPrinciple parameterAnnotation = parameter.getParameterAnnotation(AuthenticationPrinciple.class);
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
if (!parameterAnnotation.required() && !credentialManager.hasCredential(request)) {
return null;
}
String credential = credentialManager.getCredential(request);
return credentialProvider.extractMember(credential);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthenticationPrinciple {
boolean required() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public interface SpringDocAuthController {
})
@ApiErrorResponse(status = HttpStatus.UNAUTHORIZED, instance = "/login", errorCases = {
@ErrorCase(description = "아이디 불일치", exampleMessage = "존재하지 않는 아이디 moly 입니다."),
@ErrorCase(description = "비밀번호 불일치", exampleMessage = "로그인에 실패하였습니다. 아이디 또는 비밀번호를 확인해주세요."),
@ErrorCase(description = "비밀번호 불일치", exampleMessage = "로그인에 실패하였습니다. 비밀번호를 확인해주세요."),
})
ResponseEntity<LoginResponse> login(LoginRequest request, HttpServletResponse response);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;

@Component
public class SHA2PasswordEncryptor implements PasswordEncryptor {
Expand All @@ -17,7 +17,7 @@ public SHA2PasswordEncryptor() {
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new CodeZapException(HttpStatus.INTERNAL_SERVER_ERROR, "암호화 알고리즘이 잘못 명시되었습니다.");
throw new CodeZapException(ErrorCode.INTERNAL_SERVER_ERROR, "암호화 알고리즘이 잘못 명시되었습니다.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;

import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;

@Component
public class CookieCredentialManager implements CredentialManager {
Expand All @@ -29,15 +29,25 @@ public String getCredential(final HttpServletRequest httpServletRequest) {

private void checkCookieExist(final Cookie[] cookies) {
if (cookies == null) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요.");
}
}

@Override
public boolean hasCredential(final HttpServletRequest httpServletRequest) {
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies == null) {
return false;
}
return Arrays.stream(cookies)
.anyMatch(cookie -> cookie.getName().equals(CREDENTIAL_COOKIE_NAME));
}

private Cookie extractTokenCookie(final Cookie[] cookies) {
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(CREDENTIAL_COOKIE_NAME))
.findFirst()
.orElseThrow(() -> new CodeZapException(HttpStatus.UNAUTHORIZED,
.orElseThrow(() -> new CodeZapException(ErrorCode.UNAUTHORIZED_USER,
"인증에 대한 쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요."));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public interface CredentialManager {

String getCredential(HttpServletRequest httpServletRequest);

boolean hasCredential(HttpServletRequest httpServletRequest);

void setCredential(HttpServletResponse httpServletResponse, String credential);

void removeCredential(HttpServletResponse httpServletResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import java.nio.charset.StandardCharsets;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import codezap.auth.provider.CredentialProvider;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import codezap.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -33,7 +33,7 @@ public Member extractMember(String credential) {

private void checkMatchPassword(Member member, String password) {
if (!member.matchPassword(password)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "아이디 또는 비밀번호가 일치하지 않습니다.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_PASSWORD, "비밀번호가 일치하지 않습니다.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import org.springframework.http.HttpStatus;

import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

Expand All @@ -26,13 +25,13 @@ private static String decodeBase64(String base64Credentials) {
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
return new String(credDecoded, StandardCharsets.UTF_8);
} catch (IllegalArgumentException e) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "잘못된 Base64 인코딩입니다.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "잘못된 Base64 인코딩입니다.");
}
}

private static void validateBasicAuth(String[] values) {
if (values.length != BASIC_AUTH_LENGTH || values[0].isEmpty() || values[1].isEmpty()) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "인증 정보가 올바르지 않습니다. 다시 로그인 해주세요.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "인증 정보가 올바르지 않습니다. 다시 로그인 해주세요.");
}
}
}
4 changes: 2 additions & 2 deletions backend/src/main/java/codezap/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package codezap.auth.service;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import codezap.auth.dto.LoginAndCredentialDto;
Expand All @@ -9,6 +8,7 @@
import codezap.auth.encryption.PasswordEncryptor;
import codezap.auth.provider.CredentialProvider;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import codezap.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -37,7 +37,7 @@ private void validateCorrectPassword(Member member, String password) {
String salt = member.getSalt();
String encryptedPassword = passwordEncryptor.encrypt(password, salt);
if (!member.matchPassword(encryptedPassword)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "로그인에 실패하였습니다. 아이디 또는 비밀번호를 확인해주세요.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_PASSWORD, "로그인에 실패하였습니다. 비밀번호를 확인해주세요.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface SpringDocCategoryController {
@ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = {
@ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."),
@ErrorCase(description = "카테고리 이름이 15자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 15자까지 입력 가능합니다."),
})
@ApiErrorResponse(status = HttpStatus.CONFLICT, instance = "/categories", errorCases = {
@ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다."),
})
ResponseEntity<CreateCategoryResponse> createCategory(
Expand All @@ -49,7 +51,11 @@ ResponseEntity<CreateCategoryResponse> createCategory(
@ApiResponse(responseCode = "200", description = "카테고리 수정 성공")
@ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = {
@ErrorCase(description = "카테고리 이름이 15자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 15자까지 입력 가능합니다."),
})
@ApiErrorResponse(status = HttpStatus.NOT_FOUND, instance = "/categories/1", errorCases = {
@ErrorCase(description = "해당하는 id 값인 카테고리가 없는 경우", exampleMessage = "식별자 1에 해당하는 카테고리가 존재하지 않습니다."),
})
@ApiErrorResponse(status = HttpStatus.CONFLICT, instance = "/categories", errorCases = {
@ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다."),
})
@ApiErrorResponse(status = HttpStatus.FORBIDDEN, instance = "/categories/1", errorCases = {
Expand Down
5 changes: 2 additions & 3 deletions backend/src/main/java/codezap/category/domain/Category.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

import org.springframework.http.HttpStatus;

import codezap.global.auditing.BaseTimeEntity;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -67,7 +66,7 @@ public void updateName(String name) {

public void validateAuthorization(Member member) {
if (!getMember().equals(member)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "해당 카테고리를 수정 또는 삭제할 권한이 없는 유저입니다.");
throw new CodeZapException(ErrorCode.FORBIDDEN_ACCESS, "해당 카테고리를 수정 또는 삭제할 권한이 없는 유저입니다.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;

import codezap.category.domain.Category;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;

@SuppressWarnings("unused")
public interface CategoryJpaRepository extends CategoryRepository, JpaRepository<Category, Long> {

default Category fetchById(Long id) {
return findById(id).orElseThrow(
() -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다."));
() -> new CodeZapException(ErrorCode.RESOURCE_NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다."));
}

List<Category> findAllByMemberIdOrderById(Long memberId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package codezap.category.service;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -11,6 +10,7 @@
import codezap.category.dto.response.FindAllCategoriesResponse;
import codezap.category.repository.CategoryRepository;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import codezap.template.repository.TemplateRepository;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -52,7 +52,7 @@ public void update(Member member, Long id, UpdateCategoryRequest updateCategoryR

private void validateDuplicatedCategory(String categoryName, Member member) {
if (categoryRepository.existsByNameAndMember(categoryName, member)) {
throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다.");
throw new CodeZapException(ErrorCode.DUPLICATE_CATEGORY, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다.");
}
}

Expand All @@ -62,10 +62,10 @@ public void deleteById(Member member, Long id) {
category.validateAuthorization(member);

if (templateRepository.existsByCategoryId(id)) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다.");
throw new CodeZapException(ErrorCode.CATEGORY_HAS_TEMPLATES, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다.");
}
if (category.isDefault()) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "기본 카테고리는 삭제할 수 없습니다.");
throw new CodeZapException(ErrorCode.DEFAULT_CATEGORY, "기본 카테고리는 삭제할 수 없습니다.");
}
categoryRepository.deleteById(id);
}
Expand Down
12 changes: 4 additions & 8 deletions backend/src/main/java/codezap/global/cors/CorsProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

import lombok.Getter;

@Getter
@ConfigurationProperties(prefix = "cors")
public class CorsProperties {

private final String[] allowedOrigins;
private final String[] allowedOriginsPatterns;

Expand All @@ -16,12 +20,4 @@ public CorsProperties(
this.allowedOrigins = allowedOrigins;
this.allowedOriginsPatterns = allowedOriginsPatterns;
}

public String[] getAllowedOrigins() {
return allowedOrigins;
}

public String[] getAllowedOriginsPatterns() {
return allowedOriginsPatterns;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package codezap.global.exception;

import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;

import lombok.Getter;

@Getter
public class CodeZapException extends RuntimeException {
private final HttpStatusCode httpStatusCode;

public CodeZapException(HttpStatusCode httpStatusCode, String message) {
private final ErrorCode errorCode;

public CodeZapException(ErrorCode errorCode, String message) {
super(message);
this.httpStatusCode = httpStatusCode;
this.errorCode = errorCode;
}

public ProblemDetail toProblemDetail() {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
errorCode.getHttpStatus(),
getMessage());
return GlobalExceptionHandler.setProperties(problemDetail, errorCode.getCode());
}
}
31 changes: 31 additions & 0 deletions backend/src/main/java/codezap/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package codezap.global.exception;

import org.springframework.http.HttpStatus;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode {

SPRING_GLOBAL_EXCEPTION(1000, HttpStatus.BAD_REQUEST),

INVALID_REQUEST(1101, HttpStatus.BAD_REQUEST),
CATEGORY_HAS_TEMPLATES(1102, HttpStatus.BAD_REQUEST),
DEFAULT_CATEGORY(1103, HttpStatus.BAD_REQUEST),

RESOURCE_NOT_FOUND(1201, HttpStatus.NOT_FOUND),
DUPLICATE_ID(1202, HttpStatus.CONFLICT),
DUPLICATE_CATEGORY(1203, HttpStatus.CONFLICT),

UNAUTHORIZED_USER(1301, HttpStatus.UNAUTHORIZED),
UNAUTHORIZED_ID(1302, HttpStatus.UNAUTHORIZED),
UNAUTHORIZED_PASSWORD(1303, HttpStatus.UNAUTHORIZED),
FORBIDDEN_ACCESS(1304, HttpStatus.FORBIDDEN),

INTERNAL_SERVER_ERROR(2000, HttpStatus.INTERNAL_SERVER_ERROR);

private final int code;
private final HttpStatus httpStatus;
}
Loading

0 comments on commit a5ab6e9

Please sign in to comment.