본문 바로가기

Dart & Flutter/Firebase auth + Spring boot

Firebase auth + Spring boot + FLutter 사용자 등록 및 로그인 완료 #06

지난 포스트에서는 Kakao Rest api를 이용해서 카카오 로그인을 진행하고 최종적으로 사용자 정보를 획득했다.

오늘은 Firebase 로그인을 진행한다.  진행하기 전에 간단하게 프로세스를 확인 할 텐데 ! 이것만 기억하자 ! 
우리는 로그인을 하기위해 CustomToken이 필요하다.  아래 작업은 모두 이 CustomToken을 얻기위한 작업이다.

 

Firebase admin SDK 를 이용한 java ( Spring ) 레퍼런스가 거의 없어서 Firebase 공식 문서를 참고해서 개발을 진행함.

나오같은 환경에서 Kakao로그인을 구현하고 싶은 java/Spring boot 개발자들에게 도움이 됐으면 좋겠다.

( 공식문서 보지않고 해당 글을 따라가도 기능 구현은 가능하다. 하지만 개발 중 혹시 다른 사용법을 확인할 때 사용하면 좋을 것 같다. )

Firebase 공식문서 - 참고한 카테고리

 

Firebase 공식문서 Admin SDK 소개  

 

Admin Auth API 소개  |  Firebase

Firebase Admin SDK를 사용하면 자체 서버를 Firebase 인증과 통합할 수 있습니다. 또한 Firebase Admin SDK로 사용자를 관리하거나 인증 토큰을 관리할 수 있습니다. Admin SDK에는 다음과 같은 장점이 있습니

firebase.google.com

 

Index

  • Firebase 사용자의 기존/신규 를 구분하고 분기에 따라서 사용자 정보획득
  • Firebase 사용자 정보 기반 CustomToken을 발급 및 return 
  • 클라이언트 앱 Firebase 로그인 완료

 

 

Firebase 사용자의 기존 / 신규 를 구분하고 분기에 따라서 사용자 정보획득

 

1. Firebase 사용자 정보 획득

이 프로세스 에서 포인트는 사용자가 신규사용자 인지 기존사용자 인지 확인하는 부분이다. 해당 부분은 따로 api가 존재하지 않았고 기존 유저를 조회해서 존재하지 않으면 어차피 FirebaseAuthException을 발생시켰다. 때문에 사용자를 update하는 로직으로 기존/신규 고객여부를 판단 하도록 작성해 놨다. ( 어차피 사용자 정보가 변경된다면 update 해야하기 때문에... )

 

createFirebaseCustomToken() 메서드의 일부분으로 신규/기존을 구분하고 사용자 정보를 얻는 로직이다.

 

위의 이미지 에서 기존/최초 어느 고객이든 결국에는 userRecord를 얻는다는 것이다. 그 이유는 우리가 필요한 CustomToken을 얻기 위해서는 사용자 정보를 바탕으로 만들수 있기 때문에 선행 작업으로 사용자 정보를 얻어야 된다.

 

사용자정보 회득을 완료 했기 때문에 ( 신규 사용자인 경우 createUser수행 )  CustomToken을 발급 받으면 된다.

 

 

Firebase 사용자 정보 기반 CustomToken 발급 및 return 

1. 획득한 Firebase 사용자 정보를 기반으로 CustomToken을 발급받아서 return

 

2. 수정된 파일 적용하기 

KakaoService.java ( createFirebaseCustomToken() 메서드 신규작성 및 기존소스 수정됨 )

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package com.example.testsnslogin_server.controller;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
 
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.UserRecord;
import com.google.firebase.auth.UserRecord.CreateRequest;
import com.google.firebase.auth.UserRecord.UpdateRequest;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
 
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
 
@Service
public class KakaoService {
 
    private static final Logger log = LogManager.getLogger(KakaoService.class);
 
    // 카카오 로그인 프로세스 진행 (최종 목표는 Firebase CustomToken 발행)
    public Map<String,Object> execKakaoLogin(String authorize_code) {
        Map<String,Object> result = new HashMap<String,Object>();
        
        // 1. 엑세스 토큰 받기
        String accessToken = getKakaoAccessToken(authorize_code);
        result.put("accessToken", accessToken);
        
        // 2. 사용자 정보 읽어오기 
        Map<String,Object> userInfo = getKakaoUserInfo(accessToken);
        result.put("userInfo", userInfo);
 
 
        // 3. Firebase CustomToken 발행
        if(userInfo != null) {
            try {
                result.put("customToken", createFirebaseCustomToken(userInfo));
                result.put("errYn""N");
                result.put("errMsg""");
            } catch (FirebaseAuthException e) {
                // firebase 로그인 에러
                result.put("errYn""Y");
                result.put("errMsg""FirebaseException : "+ e.getMessage());
            } catch(Exception e) {
                result.put("errYn""Y");
                result.put("errMsg""Exception : "+ e.getMessage());
            }
            
        } else {
            // 카카오 로그인 취소 or 실패
            result.put("errYn""Y");
            result.put("errMsg""Kakao Login Fail");
        }
        
        log.debug(userInfo.toString());
        return result;
    }
 
    public String getKakaoAccessToken(String authorize_code) {
        String access_Token = "";
        String refresh_Token = "";
        String reqURL = "https://kauth.kakao.com/oauth/token";
 
        try {
            URL url = new URL(reqURL);
 
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 
            //    POST 요청을 위해 기본값이 false인 setDoOutput을 true로
 
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
 
            //    POST 요청에 필요로 요구하는 파라미터 스트림을 통해 전송
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
            StringBuilder sb = new StringBuilder();
            sb.append("grant_type=authorization_code");
            sb.append("&client_id=75be45c34d7befda1bd48e88afe5fe44");  //본인이 발급받은 key
            sb.append("&redirect_uri=http://192.168.26.217:8080/kakao/sign_in");     // 본인이 설정해 놓은 경로
            sb.append("&code=" + authorize_code);
            bw.write(sb.toString());
            bw.flush();
 
            // 결과 코드가 200이라면 성공
            int responseCode = conn.getResponseCode();
            log.debug("responseCode : " + responseCode);
 
            // 요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = "";
            String result = "";
 
            while ((line = br.readLine()) != null) {
                result += line;
            }
            log.debug("response body : " + result);
 
            // Gson 라이브러리에 포함된 클래스로 JSON파싱 객체 생성
            JsonElement element = JsonParser.parseString(result);
 
            access_Token = element.getAsJsonObject().get("access_token").getAsString();
            refresh_Token = element.getAsJsonObject().get("refresh_token").getAsString();
 
            log.debug("access_token : " + access_Token);
            log.debug("refresh_token : " + refresh_Token);
 
            br.close();
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return access_Token;
    }
 
    public Map<String, Object> getKakaoUserInfo (String access_Token) {
 
        //    요청하는 클라이언트마다 가진 정보가 다를 수 있기에 HashMap타입으로 선언
        Map<String, Object> userInfo = new HashMap<>();
        String reqURL = "https://kapi.kakao.com/v2/user/me";
        try {
            URL url = new URL(reqURL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
 
            //    요청에 필요한 Header에 포함될 내용
            conn.setRequestProperty("Authorization""Bearer " + access_Token);
 
            int responseCode = conn.getResponseCode();
            log.debug("responseCode : " + responseCode);
 
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
 
            String line = "";
            String result = "";
 
            while ((line = br.readLine()) != null) {
                result += line;
            }
            log.debug("response body : " + result);
 
            JsonElement element = JsonParser.parseString(result);
 
            JsonObject properties = element.getAsJsonObject().get("properties").getAsJsonObject();
            JsonObject kakao_account = element.getAsJsonObject().get("kakao_account").getAsJsonObject();
 
            String nickname = properties.getAsJsonObject().get("nickname").getAsString();
            String email = kakao_account.getAsJsonObject().get("email").getAsString();
            String id = element.getAsJsonObject().get("id").getAsString();
            userInfo.put("id", id);
            userInfo.put("nickname", nickname);
            userInfo.put("email", email);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return userInfo;
    }
 
    public String kakaoLogout(String access_Token) {
        String reqURL = "https://kapi.kakao.com/v1/user/logout";
        try {
            URL url = new URL(reqURL);
 
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 
            //    POST 요청을 위해 기본값이 false인 setDoOutput을 true로
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
 
            //    요청에 필요한 Header에 포함될 내용
            conn.setRequestProperty("Authorization""Bearer " + access_Token);
 
            // 결과 코드가 200이라면 성공
            int responseCode = conn.getResponseCode();
            log.debug("responseCode : " + responseCode);
 
            // 요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = "";
            String result = "";
 
            while ((line = br.readLine()) != null) {
                result += line;
            }
            log.debug("response body : " + result);
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        return access_Token;
    }
 
    // 기본 적으로 유효기간은 1시간 이며 유저 정보를 이용해서 생성할 수 있는 방법이 어려개 있음. ( 공식문서 참고 )
    public String createFirebaseCustomToken(Map<String,Object> userInfo) throws Exception {
 
        UserRecord userRecord;
        String uid = userInfo.get("id").toString();
        String email = userInfo.get("email").toString();
        String displayName = userInfo.get("nickname").toString();
 
        // 1. 사용자 정보로 파이어 베이스 유저정보 update, 사용자 정보가 있다면 userRecord에 유저 정보가 담긴다.
        try {
            UpdateRequest request = new UpdateRequest(uid);
            request.setEmail(email);
            request.setDisplayName(displayName);
            userRecord = FirebaseAuth.getInstance().updateUser(request);
            
        // 1-2. 사용자 정보가 없다면 > catch 구분에서 createUser로 사용자를 생성한다.
        } catch (FirebaseAuthException e) {
 
            CreateRequest createRequest = new CreateRequest();
            createRequest.setUid(uid);
            createRequest.setEmail(email);
            createRequest.setEmailVerified(false);
            createRequest.setDisplayName(displayName);
            
            userRecord = FirebaseAuth.getInstance().createUser(createRequest);
        }
 
        // 2. 전달받은 user 정보로 CustomToken을 발행한다.
        return FirebaseAuth.getInstance().createCustomToken(userRecord.getUid());
    }
}
cs

 

 

클라이언트 앱 Firebase 로그인 완료

1. 전달받은 CustomToken 으로 Firebase에 로그인 처리를 진행한다.

Firebase signIn

 

2. 로그인 완료화면 

 

 

Firebase auth + 카카오로그인 연동 완료!!!  

 

긴글 읽어 주셔서 감사합니다. 혹시나 설명이 어려웠던 부분이 있거나 궁금한점 있으시면 댓글로 달아주세요!