Skip to content

Commit

Permalink
feat: 카카오 로그인 중 사용자 정보 확인 로직을 안드로이드에서 백엔드로 이관 (#404)
Browse files Browse the repository at this point in the history
* feat: 카카오 로그인 API 구현

* feat: providerId를 loginId로 수정

* feat: 소셜 로그인 시 랜덤 생성된 비밀번호 사용

* refactor: 불필요한 api 제거

Co-authored-by: fromitive <46563149+fromitive@users.noreply.github.com>
Co-authored-by: SCY <helenason@naver.com>

* test: 로그인 로직 변경

Co-authored-by: fromitive <46563149+fromitive@users.noreply.github.com>
Co-authored-by: SCY <helenason@naver.com>

* test: MemberFixture 불필요한 함수 제거 및 통일

Co-authored-by: fromitive <fromitive@gmail.com>
Co-authored-by: Dora Choo <choo000407@naver.com>

* refactor: 불필요한 정보 제거

Co-authored-by: fromitive <fromitive@gmail.com>
Co-authored-by: Dora Choo <choo000407@naver.com>

* feat: 카카오 로그인 에러 핸들러 추가

Co-authored-by: fromitive <fromitive@gmail.com>
Co-authored-by: Dora Choo <choo000407@naver.com>

* feat: 민감 정보 로깅에서 제외

Co-authored-by: fromitive <fromitive@gmail.com>
Co-authored-by: Dora Choo <choo000407@naver.com>

---------

Co-authored-by: fromitive <46563149+fromitive@users.noreply.github.com>
Co-authored-by: SCY <helenason@naver.com>
Co-authored-by: fromitive <fromitive@gmail.com>
  • Loading branch information
4 people committed Oct 11, 2024
1 parent 6accb7a commit 28fa741
Show file tree
Hide file tree
Showing 28 changed files with 353 additions and 258 deletions.
14 changes: 3 additions & 11 deletions backend/http/auth.http
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
### 로그인 API
POST {{base-url}}/auth/login
### 카카오 로그인 API
POST {{base-url}}/auth/login/kakao
Content-Type: application/json

{
"ci": "dora1234"
}

### 회원가입 API
POST {{base-url}}/auth/signup
Content-Type: application/json

{
"ci": "poke1234567"
"accessToken": ""
}

### 토큰 재발급 API
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.zzang.chongdae.auth.config;

import com.zzang.chongdae.auth.service.AuthClient;
import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

@Configuration
public class AuthClientConfig {

@Value("${auth.connect-timeout-length}")
private Duration connectTimeoutLength;

@Value("${auth.read-timeout-length}")
private Duration readTimeoutLength;

@Bean
public AuthClient authClient() {
return new AuthClient(createRestClient());
}

private RestClient createRestClient() {
return RestClient.builder()
.requestFactory(createRequestFactory())
.requestInterceptor(new AuthClientTimeoutInterceptor())
.build();
}

private ClientHttpRequestFactory createRequestFactory() {
ClientHttpRequestFactorySettings requestFactorySettings = ClientHttpRequestFactorySettings.DEFAULTS
.withConnectTimeout(connectTimeoutLength)
.withReadTimeout(readTimeoutLength);
return ClientHttpRequestFactories.get(requestFactorySettings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.zzang.chongdae.auth.config;

import com.zzang.chongdae.auth.exception.AuthErrorCode;
import com.zzang.chongdae.global.exception.MarketException;
import java.io.IOException;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

public class AuthClientTimeoutInterceptor implements ClientHttpRequestInterceptor {

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
try {
return execution.execute(request, body);
} catch (IOException exception) {
throw new MarketException(AuthErrorCode.CLIENT_TIME_OUT);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import com.zzang.chongdae.auth.service.AuthService;
import com.zzang.chongdae.auth.service.dto.AuthInfoDto;
import com.zzang.chongdae.auth.service.dto.AuthTokenDto;
import com.zzang.chongdae.auth.service.dto.LoginRequest;
import com.zzang.chongdae.auth.service.dto.KakaoLoginRequest;
import com.zzang.chongdae.auth.service.dto.LoginResponse;
import com.zzang.chongdae.auth.service.dto.SignupRequest;
import com.zzang.chongdae.auth.service.dto.SignupResponse;
import com.zzang.chongdae.logging.config.LoggingMasked;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -26,24 +25,16 @@ public class AuthController {
private final CookieProducer cookieExtractor;
private final CookieConsumer cookieConsumer;

@PostMapping("/auth/login")
public ResponseEntity<LoginResponse> login(
@RequestBody @Valid LoginRequest request, HttpServletResponse servletResponse) {
AuthInfoDto authInfo = authService.login(request);
@LoggingMasked
@PostMapping("/auth/login/kakao")
public ResponseEntity<LoginResponse> kakaoLogin(
@RequestBody @Valid KakaoLoginRequest request, HttpServletResponse servletResponse) {
AuthInfoDto authInfo = authService.kakaoLogin(request);
addTokenToHttpServletResponse(authInfo.authToken(), servletResponse);
LoginResponse response = new LoginResponse(authInfo.authMember());
return ResponseEntity.ok(response);
}

@PostMapping("/auth/signup")
public ResponseEntity<SignupResponse> signup(
@RequestBody @Valid SignupRequest request, HttpServletResponse servletResponse) {
AuthInfoDto authInfo = authService.signup(request);
addTokenToHttpServletResponse(authInfo.authToken(), servletResponse);
SignupResponse response = new SignupResponse(authInfo.authMember());
return ResponseEntity.ok(response);
}

@PostMapping("/auth/refresh")
public ResponseEntity<Void> refresh(
HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public enum AuthErrorCode implements ErrorResponse {
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."),
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."),
INVALID_PASSWORD(HttpStatus.NOT_FOUND, "가입하지 않은 회원입니다."),
DUPLICATED_MEMBER(HttpStatus.CONFLICT, "이미 가입한 회원입니다.");
DUPLICATED_MEMBER(HttpStatus.CONFLICT, "이미 가입한 회원입니다."),
CLIENT_TIME_OUT(HttpStatus.INTERNAL_SERVER_ERROR, "시간이 초과되어 로그인 요청에 실패했습니다."),
KAKAO_LOGIN_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "카카오 로그인에 실패했습니다.");

private final HttpStatus status;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.zzang.chongdae.auth.exception;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zzang.chongdae.auth.service.dto.KakaoLoginFailResponseDto;
import com.zzang.chongdae.global.exception.MarketException;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.ResponseErrorHandler;

@Slf4j
public class KakaoLoginExceptionHandler implements ResponseErrorHandler {

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().isError();
}

@Override
public void handleError(ClientHttpResponse response) throws IOException {
throw new MarketException(getKakaoLoginErrorCode(response));
}

private AuthErrorCode getKakaoLoginErrorCode(final ClientHttpResponse response) throws IOException {
KakaoLoginFailResponseDto kakaoLoginFailResponse = objectMapper.readValue(
response.getBody(), KakaoLoginFailResponseDto.class);
log.error(kakaoLoginFailResponse.toString());
return AuthErrorCode.KAKAO_LOGIN_INTERNAL_SERVER_ERROR;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.zzang.chongdae.auth.service;

import com.zzang.chongdae.auth.exception.KakaoLoginExceptionHandler;
import com.zzang.chongdae.auth.service.dto.KakaoLoginResponseDto;
import com.zzang.chongdae.member.domain.AuthProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.client.RestClient;

@RequiredArgsConstructor
public class AuthClient {

private static final String BEARER_HEADER_FORMAT = "Bearer %s";
private static final String GET_KAKAO_USER_INFO_URI = "https://kapi.kakao.com/v2/user/me";

private final RestClient restClient;

public String getKakaoUserInfo(String accessToken) {
KakaoLoginResponseDto responseDto = restClient.get()
.uri(GET_KAKAO_USER_INFO_URI)
.header(HttpHeaders.AUTHORIZATION, createAuthorization(accessToken))
.retrieve()
.onStatus(new KakaoLoginExceptionHandler())
.body(KakaoLoginResponseDto.class);
return AuthProvider.KAKAO.buildLoginId(responseDto.id().toString()); // TODO: NPE 처리 고려하기
}

private String createAuthorization(String accessToken) {
return BEARER_HEADER_FORMAT.formatted(accessToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package com.zzang.chongdae.auth.service;

import com.zzang.chongdae.auth.exception.AuthErrorCode;
import com.zzang.chongdae.auth.service.dto.AuthInfoDto;
import com.zzang.chongdae.auth.service.dto.AuthMemberDto;
import com.zzang.chongdae.auth.service.dto.AuthTokenDto;
import com.zzang.chongdae.auth.service.dto.LoginRequest;
import com.zzang.chongdae.auth.service.dto.SignupRequest;
import com.zzang.chongdae.auth.service.dto.KakaoLoginRequest;
import com.zzang.chongdae.global.exception.MarketException;
import com.zzang.chongdae.member.domain.AuthProvider;
import com.zzang.chongdae.member.exception.MemberErrorCode;
import com.zzang.chongdae.member.repository.MemberRepository;
import com.zzang.chongdae.member.repository.entity.MemberEntity;
import com.zzang.chongdae.member.service.NicknameGenerator;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
Expand All @@ -23,26 +22,23 @@ public class AuthService {
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private final NicknameGenerator nickNameGenerator;

public AuthInfoDto login(LoginRequest request) {
String password = passwordEncoder.encode(request.ci());
MemberEntity member = memberRepository.findByPassword(password)
.orElseThrow(() -> new MarketException(AuthErrorCode.INVALID_PASSWORD));
return createTokenByMember(member);
private final AuthClient authClient;

public AuthInfoDto kakaoLogin(KakaoLoginRequest request) {
String loginId = authClient.getKakaoUserInfo(request.accessToken());
AuthProvider provider = AuthProvider.KAKAO;
MemberEntity member = memberRepository.findByLoginId(loginId)
.orElseGet(() -> signup(provider, loginId));
return login(member);
}

@Transactional
public AuthInfoDto signup(SignupRequest request) {
String password = passwordEncoder.encode(request.ci());
if (memberRepository.existsByPassword(password)) {
throw new MarketException(AuthErrorCode.DUPLICATED_MEMBER);
}
MemberEntity member = new MemberEntity(nickNameGenerator.generate(), password);
MemberEntity savedMember = memberRepository.save(member);
return createTokenByMember(savedMember);
private MemberEntity signup(AuthProvider provider, String loginId) {
String password = passwordEncoder.encode(UUID.randomUUID().toString());
MemberEntity member = new MemberEntity(nickNameGenerator.generate(), provider, loginId, password);
return memberRepository.save(member);
}

private AuthInfoDto createTokenByMember(MemberEntity member) {
private AuthInfoDto login(MemberEntity member) {
AuthMemberDto authMember = new AuthMemberDto(member);
AuthTokenDto authToken = jwtTokenProvider.createAuthToken(member.getId().toString());
return new AuthInfoDto(authMember, authToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.zzang.chongdae.auth.service.dto;

public record KakaoLoginFailResponseDto(String msg,
Long code) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.zzang.chongdae.auth.service.dto;

public record KakaoLoginRequest(String accessToken) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.zzang.chongdae.auth.service.dto;

public record KakaoLoginResponseDto(Long id) {
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.zzang.chongdae.comment.service.dto.CommentRoomInfoResponse;
import com.zzang.chongdae.comment.service.dto.CommentRoomStatusResponse;
import com.zzang.chongdae.comment.service.dto.CommentSaveRequest;
import com.zzang.chongdae.logging.config.LoggingMasked;
import com.zzang.chongdae.member.repository.entity.MemberEntity;
import jakarta.validation.Valid;
import java.net.URI;
Expand Down Expand Up @@ -55,6 +56,7 @@ public ResponseEntity<CommentRoomStatusResponse> updateCommentRoomStatus(
return ResponseEntity.ok(response);
}

@LoggingMasked
@GetMapping("/comments/messages")
public ResponseEntity<CommentAllResponse> getAllComment(
@RequestParam(value = "offering-id") Long offeringId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@Slf4j
Expand All @@ -28,6 +30,15 @@ public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws IOException {

if (!(handler instanceof HandlerMethod handlerMethod)) {
return;
}
Method method = handlerMethod.getMethod();
if (method.isAnnotationPresent(LoggingMasked.class)) {
return;
}

if (isMultipart(request)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.zzang.chongdae.logging.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggingMasked {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.zzang.chongdae.member.domain;

public enum AuthProvider {

KAKAO;

public String buildLoginId(String loginId) {
return this.name() + loginId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<MemberEntity, Long> {

Optional<MemberEntity> findByPassword(String password);

boolean existsByPassword(String password);

boolean existsByNickname(String nickname);

Optional<MemberEntity> findByLoginId(String loginId);
}
Loading

0 comments on commit 28fa741

Please sign in to comment.