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

Merged
merged 24 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7ed3fc5
[refac] make user info dto
kgy1008 Jan 13, 2025
b56b05b
[refac] extract external service from transaction
kgy1008 Jan 13, 2025
1e307da
[refac] handle feign exception
kgy1008 Jan 13, 2025
d68aef8
[refac] rename variable
kgy1008 Jan 13, 2025
9f9e1a2
[refac] apply facade pattern
kgy1008 Jan 13, 2025
8c8a177
[refac] use final keyword
kgy1008 Jan 13, 2025
25cad78
[refac] use final keyword
kgy1008 Jan 13, 2025
b867761
[refac] delete unnecessary transaction
kgy1008 Jan 13, 2025
bf4b1dc
[refac] delete unnecessary logout logic in validation
kgy1008 Jan 13, 2025
b13930f
[refac] add transactional annotation
kgy1008 Jan 13, 2025
42488c3
[refac] extract external network from transactional
kgy1008 Jan 15, 2025
3feed8c
[refac] rename class
kgy1008 Jan 15, 2025
a6d3caf
[refac] move package
kgy1008 Jan 15, 2025
5af7c6f
[docs] update README.md
kgy1008 Jan 17, 2025
b8ee714
[refac] delete static method
kgy1008 Jan 17, 2025
85a74a6
[fix] validate null name
kgy1008 Jan 17, 2025
a981c3f
[refac] separate responsibilities by creating dedicated classes
kgy1008 Jan 17, 2025
0ab0e59
[refac] delete AuthValidator.java
kgy1008 Jan 17, 2025
2890870
[refac] summarize parameter
kgy1008 Jan 17, 2025
f094f99
[refac] refactor to use string constants
kgy1008 Jan 17, 2025
f8a682a
[refac] move location of validation logic
kgy1008 Jan 17, 2025
bf76297
[refac] divide method
kgy1008 Jan 19, 2025
0384d62
[fix] Revoke refresh token renewal on access token reissuance
kgy1008 Jan 19, 2025
d1a762c
[fix] change access modifier to public for apply transactional annota…
kgy1008 Jan 24, 2025
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());
public 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());
public 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