지난 프로젝트에서는 네이버, 카카오, 구글 로그인을 JWT를 이용해서 할 수 있었는데 이번 프로젝트에서는 코딩 테스트 문제라는 비즈니스 로직에 집중하기 위해 세션 방식으로 구현해 보고, 세션 클러스터링과 이전의 로직을 리팩토링해서 구현해 보기로 했다.
이전 코드와 비교하기
OAuth2UserService
@Slf4j
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final Environment environment;
private final UserClient userClient;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
OAuth2Response response = switch (registrationId) {
case "naver" -> new NaverResponse(oAuth2User.getAttributes());
case "kakao" -> new KakaoResponse(oAuth2User.getAttributes()); // 카카오는 권한이 없어서 이름 대신 닉네임으로 설정
case "google" -> new GoogleResponse(oAuth2User.getAttributes());
default -> null;
};
if (response == null) throw new OAuth2AuthenticationException("로그인 실패");
RequestRegisterUser user = null;
ResponseCheckUser result = userClient.isUserAlreadyRegistered(new RequestCheckUser(response.getProviderId()));
if (result.getUserId() != null) { // 기존 회원일 때
user = RequestRegisterUser.builder()
.userId(result.getUserId())
.providerId(result.getProviderId())
.status(result.getStatus())
.build();
} else { // 신규 회원일 때
String oAuth2UserId = UUID.randomUUID().toString();
Provider provider = Provider.NONE;
if (response.getProvider().contains("naver")) {
provider = Provider.NAVER;
} else if (response.getProvider().contains("kakao")) {
provider = Provider.KAKAO;
} else if (response.getProvider().contains("google")) {
provider = Provider.GOOGLE;
}
user = RequestRegisterUser.builder()
.userId(oAuth2UserId)
.provider(provider)
.providerId(response.getProviderId())
.status(UserStatus.USER)
.accessToken(userRequest.getAccessToken().getTokenValue())
.build();
userClient.register(user);
}
return new CustomOAuth2User(response, user);
}
}
OAuth2UserService는 타사 로그인을 마친 후 액세스 토큰을 얻어 타사의 리소스 서버에 접근하게 되었을 때 요청한 정보의 응답을 받아 사용자 정보를 가져온다.
요청으로부터 OAuth2User를 얻어 유저의 정보들을 가져온다.
어떤 내용이 있는지 확인하고 싶으면 oAuth2User.getAttributes()를 출력해서 확인해 볼 수 있다.
그런데 여기서 뭔가 타사 이름을 지정하는 부분이 문자열이어서 뭔가 마음에 들지 않았다. 그래서 이번 프로젝트에서는 아래와 같이 변경했다.
@Slf4j
@RequiredArgsConstructor
@Transactional
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
OAuth2Response oAuth2Response = Provider.getOAuth2Response(oAuth2User, registrationId);
Optional<User> foundUser = userRepository.findByProviderId(oAuth2Response.getProviderId());
Role role;
if (foundUser.isEmpty()) {
User newUser = User.builder()
.provider(oAuth2Response.getProvider())
.providerId(oAuth2Response.getProviderId())
.nickname(oAuth2Response.getName())
.score(0)
.role(Role.USER)
.build();
userRepository.save(newUser);
role = Role.USER;
} else {
role = foundUser.get().getRole();
}
return new CustomOAuth2User(oAuth2Response, role.name());
}
}
기존의 switch문을 Enum 내부에서 처리하도록 해서 라인 수를 줄였다.
상세한 코드는 다음 코드 블록에서 확인할 수 있다.
이제 그냥 적절한 응답 객체로 처리 결과를 저장하기만 하면 된다.
public enum Provider {
GITHUB("github"),
GOOGLE("google");
private final String registrationId;
Provider(String registrationId) {
this.registrationId = registrationId;
}
public String getRegistrationId() {
return registrationId;
}
private static Provider getOAuth2Client(String registrationId) {
return Arrays.stream(Provider.values())
.filter((e) -> e.getRegistrationId().equals(registrationId))
.findFirst()
.orElse(null);
}
public static OAuth2Response getOAuth2Response(OAuth2User oAuth2User, String registrationId) {
return switch (getOAuth2Client(registrationId)) {
case GITHUB -> new GithubResponse(oAuth2User.getAttributes());
case GOOGLE -> new GoogleResponse(oAuth2User.getAttributes());
};
}
}
자바에서 enum은 일종의 클래스이기 때문에 내부에 변수를 가질 수 있다.
다만, 처음에 값이 있도록 해야 하기 때문에 final 키워드를 붙여 생성자로만 지정할 수 있도록 한다.
이렇게 enum을 사용하면서 생기는 장점은 OAuth2UserService 로직은 건들지 않고 Provider enum 내의 로직만 추가하거나 변경하면 되기 때문에 새로운 provider를 추가하거나 유지보수하기가 편해진다는 점이다.
이전 프로젝트 이후로 공부하면서 messages나 enum 등에 대해서 배우면서 다음 프로젝트에 써봐야지 생각하고 있었는데 이번에 정말 좋은 기회가 된 것 같다. 앞으로도 배운 내용들을 적극적으로 활용해 봐야겠다.
'공부 > Spring Security' 카테고리의 다른 글
| [Spring Security] 로그인 후 로그인 페이지 접속 막기 (0) | 2024.03.24 |
|---|---|
| [Spring Security] JWT Deprecated 해결하기 (0) | 2024.03.18 |
| [Spring Security] Filter 인증 구조에 대해서 깨달은 점 (0) | 2024.01.17 |
| [Spring Security] CSRF 설정하기 (0) | 2024.01.10 |
| [Spring Security] 원하는 Filter 만들어서 사용하기 (0) | 2024.01.09 |