본문 바로가기

Oauth

Oauth란?

요즘 웹사이트 로그인 화면을 보면 아래처럼 다른 사이트의 계정을 연동하여 사용할 수 있도록 되어 있는 것을 흔히 볼 수 있다.

 

 

위와 같은 소셜 로그인은 기업 입장에서는 소규모 어플리케이션이나 스타트업 단계에 있어 인증/인가 과정에 들어가는 비용을 절감시키고, 사용자 입장에서 귀찮은 회원가입을 생략하여 클릭 몇 번으로 로그인 과정을 간소화하여 편리함을 높일 수 있다.

 

비품인 프로젝트에서도 구글 로그인을 사용하여 로그인 기능을 비교적 쉽게 구현해볼 수 있었다.

 

이러한 소셜 로그인 기능은 모두 Oauth 2.0 프로토콜을 사용하여 인증 / 인가가 이루어진다.

 

각 소셜 네트워크 플랫폼마다 인증코드를 발급받을 수 있는 방법은 상이하지만, 전체적인 프로세스는 위와 동일하게 이루어진다.

 

1. 소셜 로그인 서비스 제공자에게 Client 정보 등록 >> ClientId와 Secret Key 생성

 

2. 사용자가 로그인 버튼을 클릭하면 클라이언트 단에서 로그인 창으로 요청을 보낸다. 이 때, 각 서비스 제공자에게 제공받은 정보를 파라미터에 추가하여 요청을 보낸다.

doGoogleLogin() {
  const url = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=' +
      process.env.VUE_APP_GOOGLE_CLIENT_ID +
      '&redirect_uri=' +
      process.env.VUE_APP_GOOGLE_REDIRECT_URL +
      '&response_type=code' +
      '&scope=email profile';

  this.showSocialLoginPopup(url)
},

 

3. 정보가 맞다면, 로그인 창이 뜨고 사용자 정보를 입력하여 로그인을 진행한다.

 

4. 서비스 제공자는 인가코드를 설정된 RedirectUrl 로 Response한다.

 

5. 인가코드로 AccessToken을 요청한다.

    //     1. "인가 코드"로 "액세스 토큰" 요청
    private AccessTokenDto getToken(String code, String urlType, GoogleTokenType googleTokenType) throws JsonProcessingException {
        String redirectUrl = "";
        if (googleTokenType == GoogleTokenType.LOGIN) {
            redirectUrl = urlType.equals("local") ? redirectLocalUrl : redirectServerUrl;
        } else {
            redirectUrl = urlType.equals("local") ? redirectLocalDeleteUrl : redirectServerDeleteUrl;
        }

        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP Body 생성
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("code", code);
        body.add("client_id", clientId); // 클라이언트 Id
        body.add("client_secret", clientSecret); // 클라이언트 Secret
        body.add("redirect_uri", redirectUrl);
        body.add("grant_type", "authorization_code");
        body.add("access_type", "offline");
        body.add("approval_prompt", "force");

        // HTTP 요청 보내기1
        HttpEntity<MultiValueMap<String, String>> googleTokenRequest =
                new HttpEntity<>(body, headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://oauth2.googleapis.com/token",
                HttpMethod.POST,
                googleTokenRequest,
                String.class
        );

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱
        ObjectMapper objectMapper = new ObjectMapper(); // 받은 것을 Json형태로 파싱
        return objectMapper.readValue(response.getBody(), AccessTokenDto.class);
    }

 

6. 반환된 AccessToken으로 사용자 정보를 요청한다.

 

    // 2. 토큰으로 구글 로그인 API 호출 : "액세스 토큰"으로 "구글 사용자 정보" 가져오기
    private GoogleUserInfoDto getGoogleUserInfo(AccessTokenDto accessToken) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken.getAccess_token());
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> googleUserInfoRequest = new HttpEntity<>(headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://www.googleapis.com/oauth2/v2/userinfo",
                HttpMethod.GET,
                googleUserInfoRequest,
                String.class
        );

        ObjectMapper objectMapper = new ObjectMapper();

        return objectMapper.readValue(response.getBody(), GoogleUserInfoDto.class);
    }

 

 

위의 과정까지가 구글 사용자 정보를 얻기까지의 과정이다. 해당 정보들을 모두 민감한 개인정보를 담고 있으므로, 클라이언트에게 Response할 때, 보안에 유의해야한다.

비품인 프로젝트의 경우에는 Spring Security + Jwt + Cookie를 사용하여 사용자 정보를 전달하였다.

Jwt + Cookie 의 취약점 보완을 위해 서버에서 쿠키를 생성하고, HTTP Only 설정으로 브라우저에서 쿠키에 접근할 수 없게 하였다. 또한 Secure 설정으로 HTTPS 통신에만 쿠키를 전송하도록 설정했다.

 

        // 4. JWT 토큰 반환
        ResponseCookie cookie = ResponseCookie.from(
                        JwtUtil.AUTHORIZATION_HEADER,
                        URLEncoder.encode(createdAccessToken, "UTF-8")).
                path("/").
                httpOnly(true).
                sameSite("None").
                secure(true).
                maxAge(JwtUtil.ACCESS_TOKEN_TIME).
                build();
        httpServletResponse.addHeader("Set-Cookie", cookie.toString());

 

 

참고 자료

 

OAuth 프로토콜의 이해와 활용 2 - OAuth란 무엇인가?

앞에서는 OAuth가 왜 필요하고 어떻게 발전해 왔는지 알아보았습니다. 이번시간에는 OAuth가 무엇이고, 어떻게 흘러가는지 알아보겠습니다. OAuth는 위와 같은 플로우로 이루어 집니다.. 라고 하면

gdtbgl93.tistory.com