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

[refac] 인증 비즈니스 로직 리팩토링 #253

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


# 🏗️Architecture
![한끼아키텍쳐](https://github.com/user-attachments/assets/e2c71966-893a-478e-bb68-26f66ecd17de)
![한끼아키텍쳐](https://github.com/user-attachments/assets/3ed4c337-3e4f-426b-999e-8f1c0c796cd8)

<br><br>
# 📍ERD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.auth.controller.request.UserLoginRequest;
import org.hankki.hankkiserver.api.auth.service.AuthService;
import org.hankki.hankkiserver.api.auth.service.AuthFacade;
import org.hankki.hankkiserver.api.auth.service.response.UserLoginResponse;
import org.hankki.hankkiserver.api.auth.service.response.UserReissueResponse;
import org.hankki.hankkiserver.api.common.annotation.UserId;
Expand All @@ -24,32 +24,31 @@
@RequestMapping("/api/v1")
public class AuthController {

private final AuthService authService;
private final AuthFacade authFacade;

@PostMapping("/auth/login")
public HankkiResponse<UserLoginResponse> login(@RequestHeader(HttpHeaders.AUTHORIZATION) final String token,
@Valid @RequestBody final UserLoginRequest request) {
UserLoginResponse response = authService.login(token, request);
UserLoginResponse response = authFacade.login(token, request);
return HankkiResponse.success(CommonSuccessCode.OK, response);
}

@PatchMapping("/auth/logout")
public HankkiResponse<Void> signOut(@UserId final Long userId) {
authService.logout(userId);
authFacade.logout(userId);
return HankkiResponse.success(CommonSuccessCode.OK);
}

@DeleteMapping("/auth/withdraw")
public HankkiResponse<Void> withdraw(@UserId final Long userId,
@Nullable @RequestHeader("X-Apple-Code") final String code) {
authService.withdraw(userId, code);
authFacade.withdraw(userId, code);
return HankkiResponse.success(CommonSuccessCode.NO_CONTENT);
}

@PostMapping("/auth/reissue")
public HankkiResponse<UserReissueResponse> reissue(
@RequestHeader(HttpHeaders.AUTHORIZATION) final String refreshToken) {
UserReissueResponse response = authService.reissue(refreshToken);
public HankkiResponse<UserReissueResponse> reissue(@RequestHeader(HttpHeaders.AUTHORIZATION) final String token) {
UserReissueResponse response = authFacade.reissue(token);
return HankkiResponse.success(CommonSuccessCode.OK, response);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package org.hankki.hankkiserver.api.auth.controller.request;

import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.hankki.hankkiserver.domain.user.model.Platform;

public record UserLoginRequest(
@Nullable
String name,
@NotBlank
String platform
@NotNull
Platform platform
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.hankki.hankkiserver.api.auth.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.auth.controller.request.UserLoginRequest;
import org.hankki.hankkiserver.api.auth.service.response.UserInfoResponse;
import org.hankki.hankkiserver.api.auth.service.response.UserLoginResponse;
import org.hankki.hankkiserver.api.auth.service.response.UserReissueResponse;
import org.hankki.hankkiserver.api.user.service.UserFinder;
import org.hankki.hankkiserver.api.user.service.UserInfoFinder;
import org.hankki.hankkiserver.auth.jwt.Token;
import org.hankki.hankkiserver.domain.user.model.User;
import org.hankki.hankkiserver.domain.user.model.UserInfo;
import org.hankki.hankkiserver.external.openfeign.oauth.SocialInfoResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AuthFacade {

private final UserFinder userFinder;
private final UserInfoFinder userInfoFinder;
private final ExternalService externalService;
private final AuthService authService;

public UserLoginResponse login(final String token, final UserLoginRequest request) {
SocialInfoResponse response = externalService.getUserInfo(token, request.platform(), request.name());
UserInfoResponse userInfoResponse = UserInfoResponse.of(request.platform(), response.serialId(),
response.name(), response.email());
return authService.saveOrGetUser(userInfoResponse);
}

public void withdraw(final long userId, final String code) {
User user = userFinder.getUser(userId);
externalService.revoke(user.getPlatform(), code, user.getSerialId());
authService.deleteUser(user);
}

@Transactional
public void logout(final long userId) {
UserInfo findUserInfo = userInfoFinder.getUserInfo(userId);
findUserInfo.updateRefreshToken(null);
}

@Transactional
public UserReissueResponse reissue(final String refreshToken) {
Token issuedTokens = authService.generateAccessToken(refreshToken);
return UserReissueResponse.of(issuedTokens);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@

import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.auth.controller.request.UserLoginRequest;
import org.hankki.hankkiserver.api.auth.service.response.UserInfoResponse;
import org.hankki.hankkiserver.api.auth.service.response.UserLoginResponse;
import org.hankki.hankkiserver.api.auth.service.response.UserReissueResponse;
import org.hankki.hankkiserver.api.user.service.UserFinder;
import org.hankki.hankkiserver.api.user.service.UserInfoFinder;
import org.hankki.hankkiserver.api.user.service.UserInfoUpdater;
import org.hankki.hankkiserver.api.user.service.UserUpdater;
import org.hankki.hankkiserver.auth.jwt.JwtProvider;
import org.hankki.hankkiserver.auth.jwt.JwtValidator;
import org.hankki.hankkiserver.auth.jwt.Token;
import org.hankki.hankkiserver.common.exception.UnauthorizedException;
import org.hankki.hankkiserver.domain.user.model.Platform;
import org.hankki.hankkiserver.domain.user.model.User;
import org.hankki.hankkiserver.domain.user.model.UserInfo;
import org.hankki.hankkiserver.domain.user.model.UserStatus;
import org.hankki.hankkiserver.event.EventPublisher;
import org.hankki.hankkiserver.event.user.CreateUserEvent;
import org.hankki.hankkiserver.external.openfeign.oauth.SocialInfoResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -33,44 +34,29 @@ public class AuthService {
private final UserInfoUpdater userInfoUpdater;
private final JwtProvider jwtProvider;
private final JwtValidator jwtValidator;
private final OAuthProviderFactory oAuthProviderFactory;
private final EventPublisher eventPublisher;

@Transactional
public UserLoginResponse login(final String token, final UserLoginRequest request) {
Platform platform = Platform.getEnumPlatformFromStringPlatform(request.platform());
SocialInfoResponse socialInfo = getSocialInfo(token, platform, request.name());
Optional<User> user = userFinder.findUserByPlatFormAndSeralId(platform, socialInfo.serialId());
protected UserLoginResponse saveOrGetUser(final UserInfoResponse userInfo) {
Optional<User> user = userFinder.findUserByPlatFormAndSeralId(userInfo.platform(), userInfo.serialId());
boolean isRegistered = isRegistered(user);
User findUser = loadOrCreateUser(user, platform, socialInfo);
User findUser = loadOrCreateUser(user, userInfo);
Token issuedToken = generateTokens(findUser.getId());
return UserLoginResponse.of(issuedToken, isRegistered);
}

@Transactional
public void logout(final long userId) {
UserInfo findUserInfo = userInfoFinder.getUserInfo(userId);
findUserInfo.updateRefreshToken(null);
}

@Transactional
public void withdraw(final long userId, final String code) {
User user = userFinder.getUser(userId);
Platform platform = user.getPlatform();
OAuthProvider oAuthProvider = oAuthProviderFactory.findProvider(platform);
oAuthProvider.requestRevoke(code, user.getSerialId());
protected void deleteUser(final User user) {
user.softDelete();
userInfoFinder.getUserInfo(userId).softDelete();
userInfoFinder.getUserInfo(user.getId()).softDelete();
}

@Transactional
public UserReissueResponse reissue(final String refreshToken) {
Long userId = jwtProvider.getSubject(refreshToken.substring(BEARER.length()));
protected Token generateAccessToken(final String refreshToken) {
String strippedToken = refreshToken.substring(BEARER.length());
long userId = jwtProvider.getSubject(strippedToken);
validateRefreshToken(refreshToken, userId);
UserInfo findUserInfo = userInfoFinder.getUserInfo(userId);
Token issuedTokens = jwtProvider.issueTokens(userId, getUserRole(userId));
findUserInfo.updateRefreshToken(issuedTokens.refreshToken());
return UserReissueResponse.of(issuedTokens);
String accessToken = jwtProvider.generateAccessToken(userId, getUserRole(userId));
return Token.of(accessToken, strippedToken);
}

private Token generateTokens(final long userId) {
Expand All @@ -80,63 +66,49 @@ private Token generateTokens(final long userId) {
return issuedTokens;
}

private String getUserRole(final long userId) {
return userFinder.getUser(userId).getRole().getValue();
private void validateRefreshToken(final String refreshToken, final long userId) {
jwtValidator.validateRefreshToken(refreshToken);
String storedRefreshToken = userInfoFinder.getUserInfo(userId).getRefreshToken();
jwtValidator.checkTokenEquality(refreshToken, storedRefreshToken);
}

private boolean isRegistered(final Optional<User> user) {
return user.map(u -> u.getStatus() == ACTIVE)
.orElse(false);
}

private SocialInfoResponse getSocialInfo(final String providerToken, final Platform platform, final String name) {
OAuthProvider oAuthProvider = oAuthProviderFactory.findProvider(platform);
return oAuthProvider.getUserInfo(providerToken, name);
}

private User loadOrCreateUser(final Optional<User> findUser, final Platform platform, final SocialInfoResponse socialInfo) {
return findUser.map(user -> updateOrGetUserInfo(user, user.getStatus(), socialInfo))
.orElseGet(() -> createNewUser(socialInfo, platform));
private User loadOrCreateUser(final Optional<User> findUser, final UserInfoResponse userInfo) {
return findUser.map(user -> updateOrGetUserInfo(user, user.getStatus(), userInfo))
.orElseGet(() -> createNewUser(userInfo, userInfo.platform()));
}

private User updateOrGetUserInfo(final User user, final UserStatus status, final SocialInfoResponse socialInfo) {
private User updateOrGetUserInfo(final User user, final UserStatus status, final UserInfoResponse userInfo) {
if (status == ACTIVE) {
return user;
}
return updateUserInfo(user, socialInfo);
return updateUserInfo(user, userInfo);
}

private User updateUserInfo(final User user, final SocialInfoResponse socialInfo) {
user.rejoin(socialInfo);
userInfoFinder.getUserInfo(user.getId()).updateNickname(socialInfo.name());
private User updateUserInfo(final User user, final UserInfoResponse userInfo) {
user.rejoin(userInfo.name(), userInfo.email());
userInfoFinder.getUserInfo(user.getId()).updateNickname(userInfo.name());
return user;
}

private User createNewUser(final SocialInfoResponse socialInfo, final Platform platform) {
User newUser = createUser(socialInfo.name(), socialInfo.email(), socialInfo.serialId(), platform);
private User createNewUser(final UserInfoResponse userInfo, final Platform platform) {
User newUser = createUser(userInfo.name(), userInfo.email(), userInfo.serialId(), platform);
saveUserAndUserInfo(newUser);
eventPublisher.publish(CreateUserEvent.of(newUser.getId(), newUser.getName(), newUser.getPlatform().toString()));
return newUser;
}

private String getRefreshToken(final long userId) {
return userInfoFinder.getUserInfo(userId).getRefreshToken();
private String getUserRole(final long userId) {
return userFinder.getUser(userId).getRole().getValue();
}

private void saveUserAndUserInfo(final User user) {
userUpdater.saveUser(user);
UserInfo userInfo = UserInfo.createMemberInfo(user, null);
userInfoUpdater.saveUserInfo(userInfo);
}

private void validateRefreshToken(final String refreshToken, final Long userId) {
try {
jwtValidator.validateRefreshToken(refreshToken);
String storedRefreshToken = getRefreshToken(userId);
jwtValidator.equalsRefreshToken(refreshToken, storedRefreshToken);
} catch (UnauthorizedException e) {
logout(userId);
throw e;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.hankki.hankkiserver.api.auth.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.domain.user.model.Platform;
import org.hankki.hankkiserver.external.openfeign.oauth.SocialInfoResponse;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ExternalService {

private final OAuthProviderFactory oAuthProviderFactory;

protected SocialInfoResponse getUserInfo(final String token, final Platform platform, final String name) {
OAuthProvider oAuthProvider = oAuthProviderFactory.findProvider(platform);
return oAuthProvider.getUserInfo(token, name);
}

protected void revoke(final Platform platform, final String code, final String serialId) {
OAuthProvider oAuthProvider = oAuthProviderFactory.findProvider(platform);
oAuthProvider.requestRevoke(code, serialId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.hankki.hankkiserver.api.auth.service.response;

import org.hankki.hankkiserver.domain.user.model.Platform;

public record UserInfoResponse(
Platform platform,
String serialId,
String name,
String email
) {
public static UserInfoResponse of(final Platform platform, final String serialId, final String name, final String email) {
return new UserInfoResponse(platform, serialId, name, email);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.hankki.hankkiserver.api.common.advice;

import feign.FeignException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.hankki.hankkiserver.api.dto.HankkiResponse;
import org.hankki.hankkiserver.common.code.AuthErrorCode;
import org.hankki.hankkiserver.common.code.BusinessErrorCode;
import org.hankki.hankkiserver.common.code.StoreErrorCode;
import org.hankki.hankkiserver.common.code.StoreImageErrorCode;
Expand Down Expand Up @@ -108,4 +110,10 @@ public HankkiResponse<Void> handleConstraintViolationException(ConstraintViolati
log.warn("handleConstraintViolationException() in GlobalExceptionHandler throw ConstraintViolationException : {}", e.getMessage());
return HankkiResponse.fail(BusinessErrorCode.BAD_REQUEST);
}

@ExceptionHandler(FeignException.class)
public HankkiResponse<Void> handleFeignException(FeignException e) {
log.warn("handleFeignException() in GlobalExceptionHandler throw FeignException : {}", e.getMessage());
return HankkiResponse.fail(AuthErrorCode.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.hankki.hankkiserver.api.config;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.common.PriceCategoryConverter;
import org.hankki.hankkiserver.api.common.UserIdArgumentResolver;
Expand All @@ -8,13 +9,12 @@
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

private final UserIdArgumentResolver userIdArgumentResolver;
private final PriceCategoryConverter priceCategoryConverter;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
Expand All @@ -23,6 +23,6 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new PriceCategoryConverter());
registry.addConverter(priceCategoryConverter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.auth.service.UserFinder;
import org.hankki.hankkiserver.api.user.service.UserFinder;
import org.hankki.hankkiserver.api.favorite.service.command.FavoriteSharedPostCommand;
import org.hankki.hankkiserver.api.favorite.service.command.FavoriteStoreDeleteCommand;
import org.hankki.hankkiserver.api.favorite.service.command.FavoriteStorePostCommand;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.ArrayList;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.auth.service.UserInfoFinder;
import org.hankki.hankkiserver.api.user.service.UserInfoFinder;
import org.hankki.hankkiserver.api.favorite.service.command.FavoriteOwnershipGetCommand;
import org.hankki.hankkiserver.api.favorite.service.command.FavoritesGetCommand;
import org.hankki.hankkiserver.api.favorite.service.command.FavoritesWithStatusGetCommand;
Expand Down
Loading