요즘 Flutter와 SpringBoot를 사용해 자동화 추천 알고리즘을 적용한 서비스 어플리케이션을 제작하고 있다.
과학기술정보통신부에서 주관하고 있는 해커톤을 위한 프로젝트인데, 사용자의 정보를 저장하고 활용하기 위해 로그인 방법에 대해 고민하던 중 얼마 전 주변에서 카카오 Oauth 로그인 기능을 사용하면 별 다른 복잡한 절차 없이 앱과 연동이 가능하다고 들어서 시도하게 되었다.
이 전에도 운영하던 코딩 스터디에서 Flutter에서의 Oauth 연동을 시도하긴 했었는데, 관련 지식이 없는 상태에서 막무가내로 시도하다 보니 힘들었던 기억이 있었지만 이번에 다시 차근차근 찾아보며 시도하니 서비스 서버의 API와 연동도 성공적으로 마칠 수 있었다.
플러터에서 iOS 카카오톡 로그인을 사용하기 위해 podfile을 설치해주어야 한다.
설치 방법은 두 가지가 있다. Visual Studio Code에서 프로젝트를 열고 해당 프로젝트의 터미널 CLI를 이용하거나, 기본 터미널을 이용할 수 있다.
명령어는 다음과 같다.
cd &{프로젝트폴더}/ios #기본 터미널을 이용할 경우 프로젝트 디렉토리로 이동 후 사용해야 함
pod install
pod update
카카오 로그인을 사용하기 위한 패키지 의존성을 추가한다.
dependencies:
flutter:
sdk: flutter
kakao_flutter_sdk_user: ^1.9.3 # 카카오 로그인 API 패키지
그 후 정상적인 패키지 로딩을 위해 아래의 명령어를 실행한다.
flutter clean
flutter pub get
flutter pub upgrade
Visual Studio Code 기준으로 프로젝트 디렉토리 내 ios 폴더를 우클릭해 Open in Xcode를 실행한다.
Xcode에서 해당 프로젝트의 iOS 폴더 내 Runner 파일을 실행해도 된다.
해당 파일을 열면 아래와 같은 화면이 출력되는데, 좌측 Runner 디렉토리의 Info 파일을 열어준다.
다음으로 Queried URL Schemes(Array)를 생성 후 아래의 아이템을 추가해줘야 한다. 이 과정은 하단의 코드를 info.plist에 직접 넣어 작업도 가능하다.
<key>LSApplicationQueriesSchemes</key>
<array>
<!-- 카카오톡으로 로그인 -->
<string>kakaokompassauth</string>
<!-- 카카오톡 공유 -->
<string>kakaolink</string>
<!-- 카카오톡 채널 -->
<string>kakaoplus</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>login</string>
<key>CFBundleURLSchemes</key>
<array>
<string>kakaod8a06ec1af8730814033bf36e2cb5cba</string>
</array>
</dict>
</array>
그 후 Runner의 Signing & Capabilities 내의 어플리케이션 Bundle Identifier를 복사해 카카오 디벨로퍼 포털에 등록해주면 된다.
카카오 디벨로퍼 계정으로 로그인 후, 내 애플리케이션 → 애플리케이션 추가하기로 사용할 애플리케이션을 생성한 후,
해당 Bundle ID를 카카오 디벨로퍼 랫폼 등록 페이지에서 등록해주면 카카오 Oauth 로그인을 사용하기 위한 기본적인 세팅은 끝난다.
본격적으로 패키지 내에서 카카오 로그인을 기능을 사용하기 위해 pubspec.yaml 파일에 패키지 의존성을 주입해줘야 한다. 이 과정은 터미널을 사용해 프로젝트 폴더에서 명령어로도 작업 가능하지만, 하위 버전인 Recommend 버전으로 설치되는 문제가 있어 최신 버전의 패키지를 불러오기 위해 수동으로 진행했다.
dependencies:
flutter:
sdk: flutter
# kakao_flutter_sdk: ^1.9.3 # 전체 추가
kakao_flutter_sdk_user: ^1.9.3 # 카카오 로그인 API 패키지
카카오 로그인를 사용하는 기능 코드를 작성하기 전에, 어떤식으로 활용해야 하는지 도큐먼트를 통해 알아보았다. 해당 시퀀스는 아래와 같은데, 축약해서 간단하게 정리하면
여기서 Kakao Access Token은 사용자를 증명하는 키로 사용되며, Kakao Refresh Token은 사용자의 액세스 토큰이 만료되었을 경우 해당 토큰을 함께 전송해 새로운 AccessToken을 발급 받기 위한 용도로 사용된다.
하지만 사용자가 앱 서비스를 사용할 때마다 카카오 서버로의 인증 과정을 계속 거치는 것은 API 사용량에도 문제가 있을뿐더러 필요 없는 리소스가 낭비되기 때문에 앱 서비스 서버에서도 카카오 로그인과 동일한 과정을 거쳐 사용자의 로그인 정보를 저장하고, 토큰을 저장하게 설계했다.
해당 AccessToken은 서비스에서 원활하게 사용하기 위해 서비스 API를 통해 서비스에 접근하기 위한 AccessToken과 토큰이 만료될 경우 재발급을 위한 RefreshToken을 받아 사용한다.
간단히 말해, 사용자가 카카오 로그인을 위한 토큰을 내 어플리케이션의 백엔드 서버로 보내면, 해당 토큰을 이용해 사용자에게 서비스 Access Token과 Refresh Token을 발급하고 카카오에서 사용자의 정보를 불러와 서버에 저장하는 방식이다.
이 과정에서 사용자의 토큰은 클라이언트의 Secure Storage를 사용해 안전하게 보관하고, 서버에서는 MySQL과 redis를 사용해 정보를 저장한다.
후에 로그인한 사용자가 다시 앱을 실행할 경우, 사용자가 서비스 액세스 토큰을 가지고 있는지, 만료되지 않았는지에 대한 조건을 확인해주면 된다.
앞서 서술한 것과 같이, 로그인을 위한 조건을 거친 뒤에 , 해당 로그인을 진행하는 코드는 다음과 같다.
카카오톡이 설치되어 있는지 여부에 따라 로그인 방식이 달라짐
카카오톡이 설치돼 있는 경우: 카카오톡으로 연결해 로그인 진행
카카오톡이 설치돼 있지 않은 경우: 앱 내 팝업을 통해 웹을 이용한 로그인 진행
로그인 하면 카카오톡 API를 통해 사용자에 대한 AccessToken을 발급 받음
void main() {
// 웹 환경에서 카카오 로그인을 정상적으로 완료하려면 runApp() 호출 전 아래 메소드 호출 필요
WidgetsFlutterBinding.ensureInitialized();
// runApp() 호출 전 Flutter SDK 초기화
KakaoSdk.init(
nativeAppKey: '네이티브앱 키',
javaScriptAppKey: "자바스크립트앱 키",
);
// setPathUrlStrategy(); //
runApp(const LoginTest());
}
class LoginTest extends StatelessWidget {
const LoginTest({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('로그인'),
backgroundColor: Colors.red,
),
body: Center(
child: ElevatedButton(
// style: style,
onPressed: () async {
if (await isKakaoTalkInstalled()) {
try {
await UserApi.instance.loginWithKakaoTalk();
print('카카오톡으로 로그인 성공');
AccessTokenInfo tokenInfo = await UserApi.instance.accessTokenInfo();
print('토큰 정보 보기 성공'
'\n회원정보: ${tokenInfo.id}'
'\n만료시간: ${tokenInfo.expiresIn} 초');
} catch (error) {
print('카카오톡으로 로그인 실패 $error');
// 사용자가 카카오톡 설치 후 디바이스 권한 요청 화면에서 로그인을 취소한 경우,
// 의도적인 로그인 취소로 보고 카카오계정으로 로그인 시도 없이 로그인 취소로 처리 (예: 뒤로 가기)
if (error is PlatformException && error.code == 'CANCELED') {
return;
}
// 카카오톡에 연결된 카카오계정이 없는 경우, 카카오계정으로 로그인
try {
await UserApi.instance.loginWithKakaoAccount();
print('카카오계정으로 로그인 성공');
AccessTokenInfo tokenInfo = await UserApi.instance.accessTokenInfo();
print('토큰 정보 보기 성공'
'\n회원정보: ${tokenInfo.id}'
'\n만료시간: ${tokenInfo.expiresIn} 초');
} catch (error) {
print('카카오계정으로 로그인 실패 $error');
}
}
} else {
try {
await UserApi.instance.loginWithKakaoAccount();
print('카카오계정으로 로그인 성공');
AccessTokenInfo tokenInfo = await UserApi.instance.accessTokenInfo();
print('토큰 정보 보기 성공'
'\n회원정보: ${tokenInfo.id}'
'\n만료시간: ${tokenInfo.expiresIn} 초');
User user = await UserApi.instance.me();
print('사용자 정보 요청 성공'
'\n회원번호: ${user.id}'
'\n닉네임: ${user.kakaoAccount?.profile?.nickname}'
'\n프로필사진: ${user.kakaoAccount?.profile?.profileImageUrl}');
} catch (error) {
print('카카오계정으로 로그인 실패 $error');
}
}
},
child: const Text('Enabled'),
),
),
),
);
}
}
앱을 실행하면 카카오 로그인을 거치고, 서비스 토큰들을 발급 받아 다음 화면에 출력하도록 했다.