Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/auth] 추가적인 메서드 분리 작업 및 테스트 추가 #6

Merged
merged 7 commits into from
Jun 20, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 13 additions & 8 deletions src/main/java/mju/chatuniv/auth/application/JwtAuthService.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package mju.chatuniv.auth.application;

import mju.chatuniv.auth.application.dto.TokenResponse;
import mju.chatuniv.auth.exception.AuthorizationInvalidEmailException;
import mju.chatuniv.auth.exception.AuthorizationInvalidPasswordException;
import mju.chatuniv.auth.infrastructure.JwtTokenProvider;
import mju.chatuniv.member.application.dto.MemberCreateRequest;
import mju.chatuniv.member.application.dto.MemberLoginRequest;
import mju.chatuniv.member.application.dto.MemberResponse;
import mju.chatuniv.member.domain.Member;
import mju.chatuniv.member.domain.MemberRepository;
import mju.chatuniv.member.exception.AuthorizationInvalidException;
import mju.chatuniv.member.exception.MemberNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -31,22 +32,26 @@ public MemberResponse register(final MemberCreateRequest memberCreateRequest) {
return MemberResponse.from(member);
}

@Transactional
@Transactional(readOnly = true)
public TokenResponse login(final MemberLoginRequest memberLoginRequest) {
Member member = memberRepository.findByEmail(memberLoginRequest.getEmail())
.orElseThrow(MemberNotFoundException::new);

if (checkInvalidLogin(member, memberLoginRequest)) {
throw new AuthorizationInvalidException(memberLoginRequest.getEmail());
}
validateLogin(member, memberLoginRequest);

String accessToken = jwtTokenProvider.createToken(memberLoginRequest.getEmail());
String accessToken = jwtTokenProvider.createAccessToken(memberLoginRequest.getEmail());

return new TokenResponse(accessToken);
}

private boolean checkInvalidLogin(final Member member, final MemberLoginRequest memberLoginRequest) {
return !member.isEmailSameWith(memberLoginRequest.getEmail()) || !member.isPasswordSameWith(memberLoginRequest.getPassword());
private void validateLogin(final Member member, final MemberLoginRequest memberLoginRequest) {
if (!member.isEmailSameWith(memberLoginRequest.getEmail())) {
throw new AuthorizationInvalidEmailException(memberLoginRequest.getEmail());
}

if (!member.isPasswordSameWith(memberLoginRequest.getPassword())) {
throw new AuthorizationInvalidPasswordException(memberLoginRequest.getPassword());
}
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mju.chatuniv.auth.exception;

public class AuthorizationInvalidEmailException extends RuntimeException {

public AuthorizationInvalidEmailException(final String email) {
super("Member 정보가 일치하지 않습니다. 요청하신 email : " + email);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mju.chatuniv.auth.exception;

public class AuthorizationInvalidPasswordException extends RuntimeException {

public AuthorizationInvalidPasswordException(final String password) {
super("Member 정보가 일치하지 않습니다. 요청하신 password : " + password);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class JwtTokenProvider {
@Value("${security.jwt.token.expire-length}")
private long validityInMilliseconds;

public String createToken(final String payload) {
public String createAccessToken(final String payload) {
Claims claims = Jwts.claims()
.setSubject(payload);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.util.Objects;

public class JwtLoginResolver implements HandlerMethodArgumentResolver {

private static final String TOKEN_SEPARATOR = " ";
Expand All @@ -32,7 +34,7 @@ public Member resolveArgument(final MethodParameter parameter, final ModelAndVie
String authorizationHeader = webRequest.getHeader(HttpHeaders.AUTHORIZATION);
validateAuthorization(authorizationHeader);

return jwtAuthService.findMemberByJwtPayload(getJwtPayload(authorizationHeader));
return jwtAuthService.findMemberByJwtPayload(getJwtPayload(Objects.requireNonNull(authorizationHeader)));
}

private void validateAuthorization(final String authorizationHeader) {
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/mju/chatuniv/config/ControllerAdvice.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package mju.chatuniv.config;

import mju.chatuniv.auth.exception.AuthorizationInvalidEmailException;
import mju.chatuniv.auth.exception.AuthorizationInvalidPasswordException;
import mju.chatuniv.auth.exception.BearerTokenNotFoundException;
import mju.chatuniv.member.exception.AuthorizationInvalidException;
import mju.chatuniv.member.exception.MemberEmailFormatInvalidException;
import mju.chatuniv.member.exception.MemberNotFoundException;
import mju.chatuniv.member.exception.MemberPasswordBlankException;
Expand All @@ -16,8 +17,13 @@
@RestControllerAdvice
public class ControllerAdvice {

@ExceptionHandler(AuthorizationInvalidException.class)
public ResponseEntity<String> handlerAuthorizationInvalidException(final AuthorizationInvalidException exception) {
@ExceptionHandler(AuthorizationInvalidEmailException.class)
public ResponseEntity<String> handlerAuthorizationInvalidEmailException(final AuthorizationInvalidEmailException exception) {
return getForbiddenResponse(exception.getMessage());
}

@ExceptionHandler(AuthorizationInvalidPasswordException.class)
public ResponseEntity<String> handlerAuthorizationInvalidPasswordException(final AuthorizationInvalidPasswordException exception) {
return getForbiddenResponse(exception.getMessage());
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package mju.chatuniv.auth.service;

import mju.chatuniv.auth.application.JwtAuthService;
import mju.chatuniv.auth.exception.AuthorizationInvalidEmailException;
import mju.chatuniv.auth.exception.AuthorizationInvalidPasswordException;
import mju.chatuniv.auth.infrastructure.JwtTokenProvider;
import mju.chatuniv.member.application.dto.MemberLoginRequest;
import mju.chatuniv.member.domain.Member;
import mju.chatuniv.member.domain.MemberRepository;
import mju.chatuniv.member.exception.MemberNotFoundException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static mju.chatuniv.fixture.member.MemberFixture.createMember;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;

@ExtendWith(MockitoExtension.class)
public class JwtAuthServiceUnitTest {

@InjectMocks
private JwtAuthService jwtAuthService;

@Mock
private MemberRepository memberRepository;

@Mock
private JwtTokenProvider jwtTokenProvider;

@DisplayName("로그인 시 일치하지 않는 존재하지 않는 멤버라면 예외를 발생한다.")
@Test
void throws_exception_when_login_with_invalid_member() {
// given
MemberLoginRequest memberLoginRequest = new MemberLoginRequest("b@b.com", "1234");

// when & then
assertThatThrownBy(() -> jwtAuthService.login(memberLoginRequest))
.isInstanceOf(MemberNotFoundException.class);
}

@DisplayName("로그인 시 일치하지 않는 일치하지 않는 이메일이면 예외를 발생시킨다.")
@Test
void throws_exception_when_login_with_invalid_email() {
// given
Member member = createMember();
MemberLoginRequest memberLoginRequest = new MemberLoginRequest("b@b.com", "1234");
given(memberRepository.findByEmail(any())).willReturn(Optional.of(member));

// when & then
assertThatThrownBy(() -> jwtAuthService.login(memberLoginRequest))
.isInstanceOf(AuthorizationInvalidEmailException.class);
}

@DisplayName("로그인 시 일치하지 않는 일치하지 않는 패스워드라면 예외를 발생시킨다.")
@Test
void throws_exception_when_login_with_invalid_password() {
// given
Member member = createMember();
MemberLoginRequest memberLoginRequest = new MemberLoginRequest("a@a.com", "12345");
given(memberRepository.findByEmail(any())).willReturn(Optional.of(member));

// when & then
assertThatThrownBy(() -> jwtAuthService.login(memberLoginRequest))
.isInstanceOf(AuthorizationInvalidPasswordException.class);
}

@DisplayName("payload로 Member를 찾을 때 유효하지 않는 payload라면 예외를 발생시킨다.")
@Test
void throws_exception_when_find_member_with_invalid_payload() {
// given
String jwtPayload = "invalidPayload";

// when & then
assertThatThrownBy(() -> jwtAuthService.findMemberByJwtPayload(jwtPayload))
.isInstanceOf(MemberNotFoundException.class);
}
}
74 changes: 74 additions & 0 deletions src/test/java/mju/chatuniv/auth/support/JwtLoginResolverTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package mju.chatuniv.auth.support;

import io.restassured.RestAssured;
import mju.chatuniv.auth.application.JwtAuthService;
import mju.chatuniv.auth.exception.BearerTokenNotFoundException;
import mju.chatuniv.member.application.dto.MemberCreateRequest;
import mju.chatuniv.member.application.dto.MemberLoginRequest;
import mju.chatuniv.member.domain.Member;
import org.apache.http.HttpHeaders;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.MethodParameter;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

@Sql(value = "/data.sql")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class JwtLoginResolverTest {

@Autowired
private JwtLoginResolver jwtLoginResolver;

@Autowired
private JwtAuthService jwtAuthService;

@LocalServerPort
private int port;

private final MethodParameter parameter = mock(MethodParameter.class);
private final ModelAndViewContainer mavContainer = mock(ModelAndViewContainer.class);
private final NativeWebRequest webRequest = mock(NativeWebRequest.class);
private final WebDataBinderFactory binderFactor = mock(WebDataBinderFactory.class);

@BeforeEach
void init() {
RestAssured.port = this.port;
}

@DisplayName("올바른 Jwt 토큰으로 접근 한 사용자에 해당하는 Member 객체를 반환한다.")
@Test
void returns_access_member() throws Exception {
// given
Member member = Member.from(1L, "a@a.com", "1234");
jwtAuthService.register(new MemberCreateRequest(member.getEmail(), member.getPassword()));
String accessToken = jwtAuthService.login(new MemberLoginRequest("a@a.com", "1234")).getAccessToken();
String header = "Bearer " + accessToken;
given(webRequest.getHeader(HttpHeaders.AUTHORIZATION)).willReturn(header);

// when
Member result = jwtLoginResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactor);

// then
assertThat(result).usingRecursiveComparison().isEqualTo(member);
}

@DisplayName("유효하는 Jwt 토큰을 가지고 있지 않다면 예외를 발생시킨다.")
@Test
void throws_exception_when_invalid_jwt_token_header() {
// when & then
assertThatThrownBy(() -> jwtLoginResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactor))
.isInstanceOf(BearerTokenNotFoundException.class);
}
}
16 changes: 16 additions & 0 deletions team/chatTeam/Auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Auth

## 23.06.11

- [x] JWT Token Login 구현
- [x] 유효하지 않은 토큰 혹은 로그인의 경우 예외 발생 구현
- [x] Filter 이용해서 유효하지 않은 인증이라면 접근을 막음, Auth 기능을 제외한 나머지 요청은 토큰이 필요하도록 구현
- [x] ArgumentResolver 이용해서 토큰 헤더를 컨트롤러에서 Member 객체로 바인딩하는 기능 추가
- [x] 인수테스트 뼈대 코드 완성
- [x] Auth API 테스트
- [x] API E2E 테스트
- [x] API 인수 테스트
- [x] Service 통합 테스트
- [x] Service 단위 테스트 (예외 발생 검증)
- [x] ArgumentResolver 테스트
- [x] Rest Docs 문서화