Skip to content

Commit

Permalink
[FEAT] FE 사용자 식별자 획득 API 추가 (#916)
Browse files Browse the repository at this point in the history
* chore: 에러 코드 추가 및 코드 이동 (단순 리팩토링)

* ✨ 비 유효한 쿠키 삭제 로직 추가

* ✨ feat: 사용자 정보 획득 api 추가

* fix: path 경로에 /me 추가

* fix: 코드 리뷰 기반 수정
  • Loading branch information
kimminkyeu authored Jan 5, 2025
1 parent 9247186 commit e8623ae
Show file tree
Hide file tree
Showing 14 changed files with 100 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import baguni.api.application.user.controller.dto.UserApiMapper;
import baguni.api.application.user.controller.dto.UserInfoApiResponse;
import baguni.api.service.user.service.UserService;
import baguni.security.annotation.LoginUserId;
import baguni.security.handler.BaguniLogoutHandler;
import baguni.security.util.CookieUtil;
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.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

Expand All @@ -23,7 +25,8 @@
public class UserApiController {

private final UserService userService;
private final BaguniLogoutHandler logoutHandler;
private final CookieUtil cookieUtil;
private final UserApiMapper userApiMapper;

@DeleteMapping
@Operation(summary = "회원 탈퇴", description = "회원 탈퇴를 하면 모든 폴더, 픽, 태그가 삭제됩니다.")
Expand All @@ -35,7 +38,20 @@ public ResponseEntity<Void> deleteUser(
HttpServletResponse response
) {
userService.deleteUser(userId);
logoutHandler.clearCookies(response);
cookieUtil.clearCookies(response);
return ResponseEntity.noContent().build();
}

@GetMapping
@Operation(summary = "로그인 회원의 정보 획득", description = "회원 식별자(ID_TOKEN) 및 이메일 등의 비민감성 정보를 획득합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "회원 정보 획득 성공")
})
public ResponseEntity<UserInfoApiResponse> getUserInfo(
@LoginUserId Long userId
) {
var userInfo = userService.getUserInfoById(userId);
var response = userApiMapper.toApiResponse(userInfo);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package baguni.api.application.user.controller.dto;

import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

import baguni.api.service.user.dto.UserInfo;

@Mapper(
componentModel = "spring",
injectionStrategy = InjectionStrategy.CONSTRUCTOR,
unmappedTargetPolicy = ReportingPolicy.ERROR
)
public interface UserApiMapper {

@Mapping(expression = "java(userInfo.idToken().toString())", target = "idToken")
UserInfoApiResponse toApiResponse(UserInfo userInfo);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package baguni.api.application.user.controller.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;

public record UserInfoApiResponse(

@NotEmpty
@Schema(description = "사용자 식별 토큰")
String idToken,

@NotEmpty
@Schema(description = "사용자 이메일")
String email,

// Nullable
@Schema(description = "사용자 이름")
String name
) {
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package baguni.api.service.user.dto;

import baguni.entity.model.user.User;
import baguni.entity.model.util.IDToken;

public record UserInfo(
Long id,
String name,
IDToken idToken,
String email
) {
public static UserInfo from(User user) {
return new UserInfo(user.getId(), user.getNickname(), user.getEmail());
return new UserInfo(user.getId(), user.getNickname(), user.getIdToken(), user.getEmail());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public UserInfo getUserInfoByToken(IDToken idToken) {
return UserInfo.from(user);
}

@Transactional(readOnly = true)
public UserInfo getUserInfoById(Long userId) {
var user = userDataHandler.getUser(userId);
return UserInfo.from(user);
}

@Transactional
public void deleteUser(Long userId) {
userDataHandler.deleteUser(userId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public enum ApiAuthErrorCode implements ApiErrorCode {
AUTH_SERVER_FAILURE
("AU-003", HttpStatus.SERVICE_UNAVAILABLE, "인증 처리 과정에 서버 오류가 발생했습니다! 개발자 문의 필요",
ErrorLevel.MUST_NEVER_HAPPEN()),

AUTH_INVALID_ID_TOKEN
("AU-004", HttpStatus.UNAUTHORIZED, "사용자 식별 토큰 (ID TOKEN)이 유효한 값이 아닙니다.",
ErrorLevel.MUST_NEVER_HAPPEN()),
;

// ------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public static ApiAuthException INVALID_AUTHENTICATION() {
return new ApiAuthException(ApiAuthErrorCode.AUTH_INVALID_AUTHENTICATION);
}

public static ApiAuthException INVALID_USER_ID_TOKEN() {
return new ApiAuthException(ApiAuthErrorCode.AUTH_INVALID_ID_TOKEN);
}

public static ApiAuthException OAUTH_TOKEN_ATTRIBUTE_NOT_FOUND(String targetTokenKey) {
return new ApiAuthException(ApiAuthErrorCode.AUTH_TOKEN_ATTRIBUTE_NOT_FOUND, targetTokenKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

import com.fasterxml.jackson.databind.ObjectMapper;

import baguni.api.service.user.service.UserService;
import baguni.common.exception.base.ApiErrorResponse;
import baguni.security.exception.ApiAuthErrorCode;
import baguni.security.util.CookieUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
* @author minkyeu kim
Expand All @@ -27,10 +27,13 @@
* case 1. 토큰이 만료된 요청이 온 경우 </br>
* case 2. 변조된 JWT를 가진 요청인 경우
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class BaguniApiAuthExceptionEntrypoint implements AuthenticationEntryPoint {

private final CookieUtil cookieUtil;

/**
* 시큐리티의 HttpServletResponse를 바구니 API ErrorResponse로 변환한다.
* Security에서 서비스 에러 코드를 보내야 프론트가 UI를 처리할 수 있기 때문이다.
Expand All @@ -39,12 +42,15 @@ public class BaguniApiAuthExceptionEntrypoint implements AuthenticationEntryPoin
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException arg2
AuthenticationException exception
) throws IOException {
log.error(exception.getMessage(), exception);
var errorResponse = ApiErrorResponse.of(ApiAuthErrorCode.AUTH_INVALID_AUTHENTICATION);
var errorStatus = errorResponse.getStatusCode().value();
var body = errorResponse.getBody();

cookieUtil.clearCookies(response);

response.setStatus(errorStatus);
if (Objects.nonNull(body)) {
var errorResponseJson = new ObjectMapper().writeValueAsString(body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@
public class BaguniLogoutHandler implements LogoutHandler, LogoutSuccessHandler {

private final CookieUtil cookieUtil;
private final SecurityProperties properties;

@Override
public void logout(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) {
clearCookies(response);
cookieUtil.clearCookies(response);
}

@Override
Expand All @@ -35,14 +34,4 @@ public void onLogoutSuccess(
) {
response.setStatus(HttpServletResponse.SC_OK);
}

/**
* @author sangwon
* 쿠키 삭제 메서드 분리 (공통으로 사용하기 위함)
* 시큐리티, 쿠키를 제거해주고 싶은 컨트롤러에서 사용하기 위해 분리
*/
public void clearCookies(HttpServletResponse response) {
cookieUtil.deleteCookie(response, properties.ACCESS_TOKEN_KEY);
cookieUtil.deleteCookie(response, "JSESSIONID");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import baguni.entity.model.user.Role;
import baguni.entity.model.util.IDToken;
import baguni.entity.model.util.IdTokenConversionException;
import baguni.security.config.JwtProperties;
import baguni.security.exception.ApiAuthException;
import io.jsonwebtoken.Claims;
Expand Down Expand Up @@ -42,7 +41,7 @@ public IDToken getUserIdToken() {
var raw = getClaims().get("id", String.class);
return IDToken.fromString(raw);
} catch (Exception e) {
throw ApiAuthException.INVALID_AUTHENTICATION();
throw ApiAuthException.INVALID_USER_ID_TOKEN();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ public void deleteCookie(HttpServletResponse response, String name) {
this.addCookie(response, name, "", 0, true);
}

/**
* @author sangwon
* 쿠키 삭제 메서드 분리 (공통으로 사용하기 위함)
* 시큐리티, 쿠키를 제거해주고 싶은 컨트롤러에서 사용하기 위해 분리
*/
public void clearCookies(HttpServletResponse response) {
deleteCookie(response, securityProps.ACCESS_TOKEN_KEY);
deleteCookie(response, securityProps.OAUTH_RETURN_URL_KEY);
deleteCookie(response, "JSESSIONID");
}

public Optional<String> findCookieValue(Cookie[] cookies, String name) {
if (cookies == null)
return Optional.empty();
Expand Down
6 changes: 1 addition & 5 deletions backend/baguni-api/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,7 @@
<!-- <logger name="root" level="debug" additivity="false">-->
<!-- <appender-ref ref="CONSOLE"/>-->
<!-- </logger>-->
<logger name="baguni.api" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="APP-LOG-FILE"/>
</logger>
<logger name="baguni.common" level="info" additivity="false">
<logger name="baguni" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="APP-LOG-FILE"/>
</logger>
Expand Down
6 changes: 1 addition & 5 deletions backend/baguni-batch/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,7 @@
<!-- <logger name="root" level="debug" additivity="false">-->
<!-- <appender-ref ref="CONSOLE"/>-->
<!-- </logger>-->
<logger name="baguni.batch" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="APP-LOG-FILE"/>
</logger>
<logger name="baguni.common" level="info" additivity="false">
<logger name="baguni" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="APP-LOG-FILE"/>
</logger>
Expand Down
8 changes: 1 addition & 7 deletions backend/baguni-ranking/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,8 @@
<!-- <appender-ref ref="CONSOLE"/>-->
<!-- </logger>-->
<!-- (1) 프로젝트 전체 로그 설정 -->
<logger name="baguni.ranking" level="info" additivity="false">
<logger name="baguni" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="APP-LOG-FILE"/>
</logger>
<logger name="baguni.common" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="APP-LOG-FILE"/>
</logger>


</configuration>

0 comments on commit e8623ae

Please sign in to comment.