[Refactoring] SNS 로그인 리펙토링

LDB·2025년 1월 7일
0

CS 지식

목록 보기
2/7

작업계기

OAuth2.0기술에대해 이론을 정리하던 중 예전에 개발한 OAuth2.0을 활용한 SNS로그인 코드를 보게 되었고 기존코드는 FeignClient을 사용하여 엑세스 토큰 및 유저 정보를 불러오는 방식을 사용했다, 하지만 기존의 코드를 분석해보니 다음과 같은 문제를 확인 할 수 있었다.

문제파악

기존에는 다음과 같은 방식으로 프로그램의 로직을 구성했다.

하지만 이런 식으로 구성하니 일반로그인 방식에는 필요하지 않는 인증서버와 리소스서버의 기능이 강제로 구현되었고 userservice에서 일반로그인과 SNS로그인 방식을 구분지어야 했다. 결과적으로 로직이 복잡해지고 가독성이 떨어지는 코드가 나왔다.

또한 이미지에는 적혀있지 않지만 Authorization서버 및 Resource서버의 도메인은 모두 달랐고 결과적으로 서버요청 기능 파일이 늘어났다. 그리고 로그인 팝업를 불러오는 요청 값은 SNS모두 달랐지만 Authorization서버 및 Resource서버에 통신할 때 요청하는 서버 URL만 다르고 사용하는 요청방식은 모두 같았다.


확인된 문제점

결과적으로 다음의 문제점을 제시하게되었다.

  1. SNS별로 Authorization서버주소, Resource서버주소 모두 다르기 때문에 주소별로 FeginClient를 만들어야한다.
  2. 똑같은 기능에 똑같은 코드의 반복하기에 중복 코드가 늘어난다.
  3. 코드의 가독성 저하

물론 기존의 FeginClient방식이 좋지않은 것은 아니다, FeginClient를 쓰면서 서버간 통신 코드가 간결해졌고 만약 Authorization서버 및 Resource서버의 도메인이 같았으면 FeginClient를 계속 사용했을 것이다,

문제해결

1번 문제해결

FeginClient 대신 RestTemplate객체, HttpEntity객체, JsonNode객체를 사용하여 서버와 통신

// FeginClient 방식
@FeignClient(value = "googleAuth", url="https://oauth2.googleapis.com", configuration = {FeignConfiguration.class})
public interface GoogleAuthApi {
    @PostMapping("/token")
    ResponseEntity<String> getAccessToken(@RequestBody GoogleRequestAccessTokenDto requestDto);
}

// RestTemplate, HttpEntity, JsonNode 방식

// Authorization 서버를 통해 accesstoken 발급
private String getAccessToken(UserType userType, String authorizationCode){
    MultiValueMap<String,Object> params = new LinkedMultiValueMap<>();
    
    String accesstokenUrl = environment.getProperty("spring.OAuth2."+userType+".Authorization-url");
    
    // 각 sns별 Authorization 서버주소
    params.add("code",authorizationCode);
    params.add("client_id", environment.getProperty("spring.OAuth2."+userType+".client-id"));
    params.add("client_secret",environment.getProperty("spring.OAuth2."+userType+".client-secret"));
    params.add("redirect_uri", environment.getProperty("spring.OAuth2."+userType+".callback-url"));
    params.add("grant_type", "authorization_code"); 

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    HttpEntity entity = new HttpEntity(params, headers);

    ResponseEntity<JsonNode> responseNode = 
        restTemplate.exchange(accesstokenUrl, HttpMethod.POST, entity, JsonNode.class);
        
    JsonNode accessTokenNode = responseNode.getBody();

    return accessTokenNode.get("access_token").asText();
}
  • RestTemplate : Spring에서 지원하는 간편하게 Rest 방식 API를 호출할 수 있는 Spring 내장 클래스
  • HttpEntity : HTTP요청또는 응답에 해당하는 HttpHeader와 HttpBody를 포함하는 클래스
  • JsonNode : 일종의 Json 타입의 트리자료구조 형태를 가진 객체타입 JsonNode를 사용하면서 복잡한 구조의 Response를 저장할 수 있게 되었다.

2번 문제해결

중복코드는 Authorization서버 및 Resource서버 요청코드 였기에 이 문제는 어떻게 보면 properties설정을 @Value 어노테이션으로 받아왔기에 유연하게 받는 것이 불가능해서 생긴문제였다, 그래서 Environment객체를 활용해 properties설정파일 값을 받아오는 방법을 사용했다.

// @Value 어노테이션 방식
@Value("${spring.OAuth2.Naver.client-id}")
private String NAVER_SNS_CLIENT_ID;

@Value("${spring.OAuth2.Naver.client-secret}")
private String NAVER_SNS_CLIENT_SECRET;

// Environment 방식
environment.getProperty("spring.OAuth2."+userType+".client-id"));
environment.getProperty("spring.OAuth2."+userType+".client-secret"));

위의 방식처럼 userType을 변수로 받아 SNS별로 설정값이 다르게 입력되도록 변경했다.

3번 문제해결

  • 코드 가독성문제는 기능별로 Authorization서버역할과 Resource서버의 역할을 확실히 구분지어 주석으로 작성

결과

결과적으로 로직이 이렇게 수정되었다.

  1. 인증 및 유저정보를 가져오는 기능은 중복이 되기에 userService에서 처리를 하도록 변경했다.
  2. 역할분배를 확실히 하기위해 일반로그인은 제외시켰다.

느낀점

리펙토링을 작업하다보면 항상 느끼는 것 이지만 예전의 내가 얼마나 생각없이 코드를 작성했는지 볼 수 있다. 하지만 그 당시의 나는 그것이 최선이었고 조금씩이나마 성장하고 있다는 느낌을 받을 수 있어서 나는 리펙토링 작업을 좋아한다. 앞으로도 예전에 작성한 코드를 이런식으로 리펙토링 해봐야겠다는 생각이 들었다.

Repository 주소

profile
가끔은 정신줄 놓고 멍 때리는 것도 필요하다.

0개의 댓글

관련 채용 정보