본문 바로가기

관리 메뉴

OAuth 2.0으로 Google의 Email 주소 받아오기(2) 본문

Spring

OAuth 2.0으로 Google의 Email 주소 받아오기(2)

SaintsP 2019. 2. 12. 14:14

이 포스트를 보시기 전에 OAuth 2.0에 대한 기본적인 이해 및 Google Client 설정이 필요합니다. 다음의 포스트를 참고하세요 



OAuth 2.0 정리


OAuth 2.0으로 Google의 Email 주소 받아오기(1)




드디어 Spring으로 만들어진 Web에서 OAuth 2.0을 통해 사용자의 Google Email을 받아오는 테스트 코드를 작성하겠습니다. 


(이 테스트는 특정 Oauth 라이브러리를 사용하지 않고 HTTP 프로토콜만 사용하여 진행하였으며, Google 공식 문서를 참고하여 작성하였습니다.)



OAuth 2.0은 기본적으로 HTTPS를 사용하기 때문에, 로컬 테스트에서도 HTTPS 설정이 되어있어야 합니다. 제가 참고한 HTTPS 테스트 환경 설정 포스트를 공유하니, 필요하신 분들은 참고하셔서 HTTPS 로컬 테스트 환경을 구축하시면 되겠습니다.



< 로컬에서 https 테스트 환경 구축하기 >




그럼 Spring 코드를 보시겠습니다.



(이번 테스트는 Google을 기준으로 작성됩니다. Resource Server별로 설정법이 조금씩 다르니, 다른 Resource Server를 테스트 하실 때에는 해당 서버의 OAuth 문서를 확인하세요.)


사용된 라이브러리는 httpcore, httpclient, jackson 입니다. pom.xml은 다음과 같습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        <!-- httpcore -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.6</version>
        </dependency>
        
        <!-- httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>        
 
        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.4</version>
        </dependency>
cs




가장 먼저 할 것은 로그인 URL을 생성하는일입니다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @RequestMapping("/login")
    public String loginPage(Model model) {
 
        // 구글 로그인 URL 생성
        String googleUrl = "https://accounts.google.com/o/oauth2/v2/auth?" 
                + "scope=email" 
                + "&response_type=code" 
                + "&state=security_token%3D138r5719ru3e1%26url%3Dhttps://oauth2.example.com/token" 
                + "&client_id=" + GOOGLE_CLIENT_ID
                + "&redirect_uri=" + GOOGLE_REDIRECT_URI
                + "&access_type=offline";
                
        model.addAttribute("googleUrl", googleUrl);
        
        return "login";
        
    }
cs


parameter의 client_id와 redirect_uri는 이전 포스팅에서 신청한 것들을 적어주시면 됩니다. 


login.jsp에서는 <a href="${googleUrl }">구글 로그인</a>로 컨트롤러에서 생성한 URL을 가져다 쓰시면 됩니다. 


사용자가 Login 페이지에서 이 링크를 클릭하면 google 로그인 화면이 나오고, scope로 요청한 Protected Resource에 대한 권한을 인가 한 후 redirect_url로 리다이렉트 됩니다.


사용자가 권한을 인가하면 Authorization Server에서 Client에게 Authorization Code를 get parameter로 보내줍니다. 이를 콜백 컨트롤러에서 @RequestParam("code")로 받아올 수 있습니다. 이 Authorization Code는 이 다음 단계인 Access Token을 발급 받기 위해 꼭 필요한 정보입니다.



다음으로 콜백 주소인 redirect_url에 대한 처리를 해줍니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    // 구글 로그인 콜백
    @RequestMapping(value = "/social/googleLogin")
    public String googleLogin(@RequestParam("code"String code, HttpSession session, Model model) throws Exception {
 
        // 코드 확인
        System.out.println("code : " + code);
        
        
        // Access Token 발급
        JsonNode jsonToken = GoogleLogin.getAccessToken(code);
        String accessToken = jsonToken.get("access_token").toString();
        String refreshToken = "";
        if(jsonToken.has("refresh_token")) {
            refreshToken = jsonToken.get("refresh_token").toString();
        }
        String expiresTime = jsonToken.get("expires_in").toString();
        System.out.println("Access Token : " + accessToken);
        System.out.println("Refresh Token : " + refreshToken);
        System.out.println("Expires Time : " + expiresTime);
 
        // Access Token으로 사용자 정보 반환
        JsonNode userInfo = GoogleLogin.getGoogleUserInfo(accessToken);
        System.out.println(userInfo.toString());
        
        String socialMail = userInfo.get("email").asText();
        
        // 사용자 정보 출력
        System.out.println("socialMail : " + socialMail);
        
        // 받아온 사용자 정보를 view에 전달
        model.addAttribute("socialMail", socialMail);
        
        return "socialLoginPage";
    }
cs


(테스트라서 Authorization Code나 Access Token 정보 등을 sysout으로 찍었지만, 실제 서버에서는 이렇게 찍으면 당연히 안됩니다.)


Client는 Authorization Server로 부터 받은 Authorization Code를 가지고 다시 Authorization Server에게 Access Token을 요청합니다. 


이 부분인 getAccessToken 메소드는 다음과 같습니다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    public static JsonNode getAccessToken(String autorize_code) {
 
        final String RequestUrl = "https://www.googleapis.com/oauth2/v4/token";
 
        final List<NameValuePair> postParams = new ArrayList<NameValuePair>();
        postParams.add(new BasicNameValuePair("grant_type""authorization_code"));
        postParams.add(new BasicNameValuePair("client_id""***************************"));
        postParams.add(new BasicNameValuePair("client_secret""***************************"));
        postParams.add(new BasicNameValuePair("redirect_uri""https://localhost:8443/social/googleLogin.do")); // 리다이렉트 URI
        postParams.add(new BasicNameValuePair("code", autorize_code)); // 로그인 과정중 얻은 code 값
 
        final HttpClient client = HttpClientBuilder.create().build();
        final HttpPost post = new HttpPost(RequestUrl);
        JsonNode returnNode = null;
 
        try {
            post.setEntity(new UrlEncodedFormEntity(postParams));
            final HttpResponse response = client.execute(post);
            final int responseCode = response.getStatusLine().getStatusCode();
 
            System.out.println("\nSending 'POST' request to URL : " + RequestUrl);
            System.out.println("Post parameters : " + postParams);
            System.out.println("Response Code : " + responseCode);
 
            // JSON 형태 반환값 처리
            ObjectMapper mapper = new ObjectMapper();
            returnNode = mapper.readTree(response.getEntity().getContent());

 
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // clear resources
        }
 
        return returnNode;
 
    }
cs



Client는 Authorization Server에게 grant_type, client_id, client_secret, redirect_uri code을 POST로 보냅니다. 


파라미터로 client_id, client_secret는 이전 Google에서 신청한 ID와 Secret을. redirect_uri는 Authorization Code를 받기 위한 링크에서 설정한 redirect_uri와 같은 uri를(이전 Google에서 신청한 리디렉트 URI와 같음), grant_type은 authorization_code로, code는 앞에서 받은 Authorization Code를 넣어주시면 됩니다.


Authorization Server는 이 요청을 검사하여 유효한 요청인지 확인 후 유효한 요청일 경우 Access Token과 Refresh Token, Expires Time 등의 정보를 JSON으로 응답합니다.



마지막으로 이 Access Token을 Resource Server에 보내 필요한 Resource를 응답 받는 부분인 getGoogleUserInfo 메소드를 보시겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    public static JsonNode getGoogleUserInfo(String autorize_code) {
 
        final String RequestUrl = "https://www.googleapis.com/oauth2/v2/userinfo";
 
        final HttpClient client = HttpClientBuilder.create().build();
        final HttpGet get = new HttpGet(RequestUrl);
 
        JsonNode returnNode = null;
        
        // add header
        get.addHeader("Authorization""Bearer " + autorize_code);
 
        try {
            final HttpResponse response = client.execute(get);
            final int responseCode = response.getStatusLine().getStatusCode();
            
            ObjectMapper mapper = new ObjectMapper();
            returnNode = mapper.readTree(response.getEntity().getContent());
            
            System.out.println("\nSending 'GET' request to URL : " + RequestUrl);
            System.out.println("Response Code : " + responseCode);
 
 
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // clear resources
        }
        return returnNode;
 
    }
cs



기본적으로 Access Token의 전송은 Header로 이루어집니다. get parameter로 전송할 수도 있지만 access log등에 노출될 위험이 있기 때문입니다. 


header에 Access Token을 추가하여 Resource Server로 요청을 보내면 Resource Server는 Access Token을 검증하고 이상이 없으면 해당하는 Resource를 JSON으로 응답합니다. 이렇게 응답 받은 Resource를 사용하시면 됩니다. 




OAuth 2.0으로 받은 Email을 view로 전송하여 alert를 띄워보면 



정상적으로 Email을 불러온 것을 확인 할 수 있습니다.








Comments