Skip to content

Commit

Permalink
[FEAT] 로그아웃 구현 및 redis를 통한 임시 유저 저장 구현 (#25)
Browse files Browse the repository at this point in the history
* [DOCS] PR 템플릿 수정

* [REFACTOR] redis 저장으로 로직 변경

* [MOVE] 이메일 관련 에러 코드 UniversityExceptionType으로 이동

* [DEL] VO 통합으로 인한 삭제

* [DEL] VO 통합으로 인한 삭제

* [DEL] 필요없는 코드 삭제

* [FEAT] redis에 저장할 임시 유저 정의

* [FIX] 요청받을 데이터 수정

* [DEL] Dto명 변경으로 인한 삭제

* [CHORE] 일부 필요없는 코드 삭제 및 데이터 길이 수정

* [REFACTOR] AuthService으로 의존성 정리

* [FEAT] redis관련 로직 구현

* [MOVE] 파일 위치 변경

* [RENAME] 파일 이름 변경

* [RENAME] 파일 이름 변경

* [FEAT] 로그아웃 구현

* [ADD] 로그아웃 Dto 추가

* [REFACTOR] 메일 서비스의 레포지토리 의존성 제거를 통한 개선

* [FEAT] redis, university 레포지토리 추가

* [ADD] secondary index TimeToLive 적용을 위한 코드 추가

* [ADD] RedisException 추가

* [ADD] RedisExceptionType enum 추가

* [FEAT] 임시 유저 저장을 위한 Redis 레포지토리 구현

* [RENAME] 파일 이름 변경

* [ADD] Id로 찾는 메서드 추가

* [FEAT] Id로 찾는 메서드 구현

* [DEL] 필요없는 의존성 제거

* [DEL] 필요없는 코드 제거

* [CHORE] 로그아웃 관련 코드 리뷰 반영

* [CHORE] 함수 이름 변경

* [CHORE] 함수 이름 변경

* [CHORE] 변수 이름 변경

* [CHORE] TTL 시간 변경

* [ADD] 매직리터럴 상수로 추가

* [DOCS] 스웨거 API 문서 작성

* [DOCS] 스웨거 API 문서 수정

* [DOCS] 스웨거 Dto 문서 추가
  • Loading branch information
mikekks committed Apr 12, 2024
1 parent c3c53bb commit 9fa7771
Show file tree
Hide file tree
Showing 37 changed files with 568 additions and 286 deletions.
32 changes: 18 additions & 14 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
# 구현 내용/방법
## 📝 PR 타입
- [ ] 기능 추가
- [ ] 기능 수정
- [ ] 기능 삭제
- [ ] 리팩토링
- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트

> 간단하게 구현한 내용과 방법에 대한 설명
>
-
-
## 📝 반영 브랜치
<!-- feat/#issue -> dev와 같이 반영 브랜치를 표시합니다 -->
<!-- closed #issue로 merge되면 issue가 자동으로 close되게 해줍니다 -->
- feat/
- closed

# 리뷰 필요
## 📝 변경 사항
<!-- 로그인 시, 구글 소셜 로그인 기능을 추가했습니다. 와 같이 작성합니다 -->

> 나중에 다시 고민해야할 내용이 있는 내용
>
> 없을 경우 작성 X

> 있을 경우 작성 후 이슈 남기고 해당 PR 링크
>
-
-
## 📝 테스트 결과
<!-- local에서 postman으로 요청한 결과를 첨부합니다, postman을 사용하지 않으면 관련 화면 캡쳐 -->

close

## 📝 To Reviewer
<!-- review 받고 싶은 point를 작성합니다 -->
78 changes: 78 additions & 0 deletions src/main/java/synk/meeteam/domain/auth/api/AuthApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package synk.meeteam.domain.auth.api;


import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import synk.meeteam.domain.auth.dto.request.AuthUserRequestDto;
import synk.meeteam.domain.auth.dto.request.#UserRequestDto;
import synk.meeteam.domain.auth.dto.request.VerifyUserRequestDto;
import synk.meeteam.domain.auth.dto.response.AuthUserResponseDto;
import synk.meeteam.domain.auth.dto.response.LogoutUserResponseDto;
import synk.meeteam.domain.auth.dto.response.ReissueUserResponseDto;
import synk.meeteam.domain.auth.dto.response.#UserResponseDto;
import synk.meeteam.domain.user.entity.User;
import synk.meeteam.security.AuthUser;


@Tag(name = "auth", description = "인증 관련 API")
public interface AuthApi {

@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다"),
@ApiResponse(responseCode = "201", description = "회원가입에 성공하였습니다"),
@ApiResponse(responseCode = "400", description = "유효하지 않은 플랫폼 인가코드입니다, 입력값이 올바르지 않습니다, 올바르지 않은 플랫폼 유형입니다")
}
)
@Operation(summary = "소셜 로그인(1)")
ResponseEntity<AuthUserResponseDto> login(
@RequestHeader(value = "authorization-code") final String authorizationCode,
@RequestBody @Valid final
AuthUserRequestDto requestDto);


@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "임시 유저 생성 및 이메일 전송에 성공하였습니다."),
}
)
@Operation(summary = "임시 유저 생성 및 이메일 전송(2)")
ResponseEntity<#UserResponseDto> createTempUserAndSendEmail(
@RequestBody @Valid #UserRequestDto requestDto
);

@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "이메일 인증 및 회원가입에 성공하였습니다."),
}
)
@Operation(summary = "이메일 인증 및 회원가입(3)")
ResponseEntity<AuthUserResponseDto> #(
@RequestBody @Valid VerifyUserRequestDto requestDto);


@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "리프레시 토큰 재발급에 성공하였습니다."),
@ApiResponse(responseCode = "400", description = "서비스에서 발급되지 않거나 이미 사용된 리프레시 토큰입니다."),
@ApiResponse(responseCode = "401", description = "기한이 만료된 리프레시 토큰입니다.")
}
)
@Operation(summary = "액세스 토큰 & 리프레시 토큰 재발급", description = "액세스 토큰 및 리프레시 토큰을 재발급 받습니다.")
ResponseEntity<ReissueUserResponseDto> reissue(HttpServletRequest request);

@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "로그아웃에 성공하였습니다."),
}
)
@Operation(summary = "로그아웃")
ResponseEntity<LogoutUserResponseDto> logout(@AuthUser final User user);
}
83 changes: 44 additions & 39 deletions src/main/java/synk/meeteam/domain/auth/api/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package synk.meeteam.domain.auth.api;

import static synk.meeteam.domain.auth.exception.AuthExceptionType.INVALID_MAIL_REGEX;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -15,96 +14,102 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import synk.meeteam.domain.auth.api.dto.request.UserAuthRequestDTO;
import synk.meeteam.domain.auth.api.dto.request.User#RequestDTO;
import synk.meeteam.domain.auth.api.dto.response.UserAuthResponseDTO;
import synk.meeteam.domain.auth.api.dto.response.UserReissueResponseDTO;
import synk.meeteam.domain.auth.api.dto.response.User#ResponseDTO;
import synk.meeteam.domain.auth.exception.AuthException;
import synk.meeteam.domain.auth.dto.request.AuthUserRequestDto;
import synk.meeteam.domain.auth.dto.request.#UserRequestDto;
import synk.meeteam.domain.auth.dto.request.VerifyUserRequestDto;
import synk.meeteam.domain.auth.dto.response.AuthUserResponseDto;
import synk.meeteam.domain.auth.dto.response.LogoutUserResponseDto;
import synk.meeteam.domain.auth.dto.response.ReissueUserResponseDto;
import synk.meeteam.domain.auth.dto.response.#UserResponseDto;
import synk.meeteam.domain.auth.service.AuthServiceProvider;
import synk.meeteam.domain.auth.service.vo.User#VO;
import synk.meeteam.domain.university.service.UniversityService;
import synk.meeteam.domain.user.entity.User;
import synk.meeteam.domain.user.entity.UserVO;
import synk.meeteam.domain.user.entity.enums.Role;
import synk.meeteam.domain.user.repository.UserRepository;
import synk.meeteam.domain.user.service.UserService;
import synk.meeteam.infra.mail.MailService;
import synk.meeteam.infra.oauth.service.vo.enums.AuthType;
import synk.meeteam.security.AuthUser;
import synk.meeteam.security.jwt.service.JwtService;

@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
@Slf4j
public class AuthController {
public class AuthController implements AuthApi {
private final AuthServiceProvider authServiceProvider;
private final JwtService jwtService;
private final MailService mailService;
private final UniversityService universityService;
private final UserRepository userRepository;
private final UserService userService;

@Value("${spring.security.oauth2.client.naver.client-id}")
private String clientId;
@Value("${spring.security.oauth2.client.naver.redirect-uri}")
private String redirectUri;

@Override
@PostMapping("/social/#")
public ResponseEntity<UserAuthResponseDTO> login(
public ResponseEntity<AuthUserResponseDto> login(
@RequestHeader(value = "authorization-code") final String authorizationCode,
@RequestBody @Valid final
UserAuthRequestDTO request, HttpServletResponse response) {
AuthUserRequestDto requestDto) {

User#VO vo = authServiceProvider.getAuthService(request.platformType())
.saveUserOrLogin(authorizationCode, request);
User#VO vo = authServiceProvider.getAuthService(requestDto.platformType())
.saveUserOrLogin(authorizationCode, requestDto);

if (vo.role() == Role.GUEST) {
return ResponseEntity.ok(UserAuthResponseDTO
return ResponseEntity.ok(AuthUserResponseDto
.of(vo.platformId(), vo.authType(), vo.name(), vo.role(), null, null));
}

UserAuthResponseDTO responseDTO = jwtService.issueToken(vo);
if (responseDTO.authType().equals(AuthType.SIGN_UP)) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(responseDTO);
}
AuthUserResponseDto responseDTO = jwtService.issueToken(vo);
return ResponseEntity.ok(responseDTO);
}

@PostMapping("/social/sign-up")
public ResponseEntity<User#ResponseDTO> #(
@RequestBody @Valid User#RequestDTO requestDTO
@Override
@PostMapping("/social/email-verify")
public ResponseEntity<#UserResponseDto> createTempUserAndSendEmail(
@RequestBody @Valid #UserRequestDto requestDto
) {
if (!universityService.isValidRegex(requestDTO.universityName(), requestDTO.email())){
throw new AuthException(INVALID_MAIL_REGEX);
}
Long universityId = universityService.getUniversityId(requestDto.universityName(), requestDto.departmentName(),
requestDto.email());

userService.updateUniversityInfo(requestDTO);
mailService.sendMail(requestDTO);
authServiceProvider.getAuthService(requestDto.platformType()).updateUniversityInfo(requestDto, universityId);
mailService.sendMail(requestDto, requestDto.platformId());

return ResponseEntity.ok(User#ResponseDTO.of(requestDTO.platformId()));
return ResponseEntity.ok(#UserResponseDto.of(requestDto.platformId()));
}

@GetMapping("/email-verify")
public ResponseEntity<UserAuthResponseDTO> verify(
@RequestParam String emailCode) {
@Override
@PostMapping("/sign-up")
public ResponseEntity<AuthUserResponseDto> #(
@RequestBody @Valid VerifyUserRequestDto requestDto) {

UserVO userVO = mailService.verify(requestDto.emailCode());
User user = authServiceProvider.getAuthService(userVO.getPlatformType())
.createSocialUser(userVO, requestDto.nickName());

User user = mailService.verify(emailCode);
User#VO vo = User#VO.of(user, user.getPlatformType(), user.getRole(), AuthType.SIGN_UP);
UserAuthResponseDTO responseDTO = jwtService.issueToken(vo);
AuthUserResponseDto responseDTO = jwtService.issueToken(vo);

return ResponseEntity.status(HttpStatus.CREATED).body(responseDTO);
}

@Override
@PostMapping("/reissue")
public ResponseEntity<UserReissueResponseDTO> reissue(HttpServletRequest request,
HttpServletResponse response) {
UserReissueResponseDTO userReissueResponseDTO = jwtService.reissueToken(request, response);
return ResponseEntity.ok().body(userReissueResponseDTO);
public ResponseEntity<ReissueUserResponseDto> reissue(HttpServletRequest request) {
ReissueUserResponseDto reissueUserResponseDto = jwtService.reissueToken(request);
return ResponseEntity.ok().body(reissueUserResponseDto);
}

@Override
@PostMapping("/logout")
public ResponseEntity<LogoutUserResponseDto> logout(@AuthUser final User user) {
return ResponseEntity.ok(jwtService.logout(user));
}

@GetMapping("/authTest")
public String authTest(HttpServletRequest request, HttpServletResponse response) {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package synk.meeteam.domain.auth.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import synk.meeteam.domain.user.entity.enums.PlatformType;

@Schema(name = "AuthUserRequestDto", description = "소셜 로그인 요청 Dto")
public record AuthUserRequestDto(
@NotNull
@Schema(description = "소셜 플랫폼 타입", example = "NAVER")
PlatformType platformType) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package synk.meeteam.domain.auth.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import synk.meeteam.domain.user.entity.enums.PlatformType;

@Schema(name = "#UserRequestDto", description = "임시 회원 가입 및 이메일 인증 요청 Dto")
public record #UserRequestDto(

@NotNull
@Schema(description = "플랫폼 Id", example = "Di7lChMGxjZVTai6d76Ho1YLDU_xL8tl1CfdPMV5SQM")
String platformId,
@NotNull
@Schema(description = "플랫폼 타입", example = "NAVER")
PlatformType platformType,
@NotNull
@Schema(description = "학사 이메일", example = "thdalsrb123@kw.ac.kr")
String email,
@NotNull
@Schema(description = "학교 이름", example = "광운대학교")
String universityName,
@NotNull
@Schema(description = "학과 이름", example = "소프트웨어학부")
String departmentName,
@NotNull
@Schema(description = "입학년도", example = "2018")
int admissionYear

) {
}
Loading

0 comments on commit 9fa7771

Please # to comment.