1. 초기화하기
main.dart
import 'package:kakao_flutter_sdk_common/kakao_flutter_sdk_common.dart'; void main() { ... // 웹 환경에서 카카오 로그인을 정상적으로 완료하려면 runApp() 호출 전 아래 메서드 호출 필요 WidgetsFlutterBinding.ensureInitialized(); // runApp() 호출 전 Flutter SDK 초기화 KakaoSdk.init( nativeAppKey: '${YOUR_NATIVE_APP_KEY}', javaScriptAppKey: '${YOUR_JAVASCRIPT_APP_KEY}', ); runApp(MyApp()); ... }
네이티브 앱키는 내 애플리케이션 - 앱 키에서 확인할 수 있다.
앱 키에서 확인한 네이티브 앱키를 카카오의 main.dart와 AndroidManifest.xml 의 activity 내부에 넣는다.
원래는 변수로 넣어야하지만 테스트를 위해 임시로 넣어둔다.
2. 카카오 로그인(토큰 받기)
try { OAuthToken token = await UserApi.instance.loginWithKakaoTalk(); print('카카오톡으로 로그인 성공 ${token.accessToken}'); } catch (error) { print('카카오톡으로 로그인 실패 $error'); }
login_page.dart
import 'package:flutter/material.dart'; import 'package:kakao_flutter_sdk/kakao_flutter_sdk.dart'; class LoginPage extends StatelessWidget { const LoginPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("카카오 로그인"), ), body: ElevatedButton( child: Text("카카오 로그인"), onPressed: () async { try { OAuthToken token = await UserApi.instance.loginWithKakaoTalk(); print('카카오톡으로 로그인 성공 ${token.accessToken}'); } catch (error) { print('카카오톡으로 로그인 실패 $error'); } }, ), ); } }
버튼을 누르면 카카오 페이지로 넘어간다. 로그인이 정상적으로 되면 토큰을 받을 수 있다.
3. 서버와 통신하기
애플리케이션과 카카오 서버 간 통신을 통해 받은 토큰을 스프링 서버로 보낸다. 스프링 서버는 받은 토큰을 카카오와 통신을 통해 검증한다.
3.1 플러터 통신 코드 만들기
플러터에서 http 통신을 쉽게 하기 위해 dio 라이브러리를 설치한다.
pubspec.yml
http.dart
import 'package:dio/dio.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; final baseUrl = "http://192.168.0.171:8080"; // mac : ipconfig getifaddr en0 final dio = Dio( BaseOptions( baseUrl: baseUrl, // 내 IP 입력 contentType: "application/json; charset=utf-8", validateStatus: (status) => true, // 200 이 아니어도 예외 발생안하게 설정 ), ); const secureStorage = FlutterSecureStorage(); // 받은 토큰을 디바이스에 저장한다.
login_page.dart
import 'package:flutter/material.dart'; import 'package:kakao_flutter_sdk/kakao_flutter_sdk.dart'; import 'package:oauthapp/_core/http.dart'; class LoginPage extends StatelessWidget { const LoginPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("카카오 로그인"), ), body: ElevatedButton( child: Text("카카오로그인"), onPressed: () async { kakaoLogin(); }, ), ); } void kakaoLogin() async { try { // 1. 크리덴셜 로그인 - 토큰 받기 OAuthToken token = await UserApi.instance.loginWithKakaoAccount(); // Bl_yCbOpzJe4vQGNCBX_cQI0VVBvdUm3AAAAAQo9dNsAAAGP3IxzfMYNwJ_muSR4 print('카카오계정으로 로그인 성공 ${token.accessToken}'); // 2. 토큰(카카오)을 스프링서버에 전달하기 (스프링 서버한테 나 인증했어!! 라고 알려주는것) await dio.get("/oauth/callback", queryParameters: {"accessToken": token.accessToken}); // 3. 토큰(스프링서버) 응답받기 // 4. 시큐어 스토리지에 저장 } catch (error) { print('카카오계정으로 로그인 실패 $error'); } } }
3.2 스프링 컨트롤러 만들기
userController
@GetMapping("/oauth/callback") public ResponseEntity<?> oauthCallBack(String accessToken) { System.out.println("스프링 서버가 받은 카카오 토큰: " +accessToken); return ResponseEntity.ok(new ApiUtil(null)); }
카카오에서 받은 토큰을 스프링 서버로 전송 완료되었다.
4. 스프링 서버에서 JWT 토큰 생성하기
카카오에서 받은 토큰을 사용하게 되면 매번 카카오를 통해 검증을 해야 한다. 따라서 애플리케이션을 통해 받은 토큰을 카카오에게 검증한 후 OAuth 가 완료되면 스프링 서버가 클라이언트에게 토큰을 발급해준다. 그럼 앱은 새롭게 받은 토큰을 통해 서버와 통신할 수 있다.
4.1 카카오로그인 Service 레이어 만들기
서비스 레이어의 역할은 다음과 같다.
1. 카카오에서 사용자 정보 요청하기
- 카카오를 통해 받은 데이터를 DB에서 조회, 데이터가 있으면 로그인, 없으면 강제 회원가입
- JWT 생성 후 앱에게 전달하기
UserService
import lombok.RequiredArgsConstructor; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import shop.mtcoding.blog._core.errors.exception.Exception400; import shop.mtcoding.blog._core.errors.exception.Exception401; import shop.mtcoding.blog._core.utils.JwtUtil; import java.util.Optional; import java.util.UUID; @RequiredArgsConstructor @Service // IoC 등록 public class UserService { @Transactional public String 카카오로그인(String kakaoAccessToken) { // 1. RestTemplate 객체 생성 RestTemplate rt = new RestTemplate(); // 2. 토큰으로 사용자 정보 받기 (PK, Email) HttpHeaders headers = new HttpHeaders(); headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); headers.add("Authorization", "Bearer "+kakaoAccessToken); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(headers); ResponseEntity<KakaoResponse.KakaoUserDTO> response = rt.exchange( "https://kapi.kakao.com/v2/user/me", HttpMethod.GET, request, KakaoResponse.KakaoUserDTO.class); // 3. 해당정보로 DB조회 (있을수, 없을수) String username = "kakao_"+response.getBody().getId(); User userPS = userJPARepository.findByUsername(username) .orElse(null); // 4. 있으면? - 조회된 유저정보 리턴 if(userPS != null){ return JwtUtil.create(userPS); }else{ // 5. 없으면? - 강제 회원가입 User user = User.builder() .username(username) .password(UUID.randomUUID().toString()) .email(response.getBody().getProperties().getNickname()+"@nate.com") .provider("kakao") .build(); User returnUser = userJPARepository.save(user); return JwtUtil.create(returnUser); } } }
4.2 ResponseDTO 만들기
앱을 통해 받은 토큰을 카카오에 검증 후, 검증이 완료되면 받을 회원 정보를 담는다.
import java.sql.Timestamp;
Timestamp 의 임포트가 달라지면 오류가 나니 주의하
KakaoResponse
package shop.mtcoding.blog.user; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.sql.Timestamp; public class KakaoResponse { @Data public static class KakaoUserDTO { private Long id; @JsonProperty("connected_at") private Timestamp connectedAt; private Properties properties; @Data class Properties { private String nickname; } } }
4.3 User 엔티티
provider 필드가 필요하다. 카카오, 네이버 등 다양한 OAuth 인증을 위해 사용된다.
User
package shop.mtcoding.blog.user; import jakarta.persistence.*; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import java.sql.Timestamp; @NoArgsConstructor @Data @Table(name = "user_tb") @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(unique = true) private String username; private String password; private String email; private String provider ; // 카카오 네이버 등 구분 @CreationTimestamp // pc -> db (날짜주입) private Timestamp createdAt; @Builder public User(Integer id, String username, String password, String email, String provider, Timestamp createdAt) { this.id = id; this.username = username; this.password = password; this.email = email; this.provider = provider; this.createdAt = createdAt; } }
4.5 컨트롤러 만들기
스프링 서버에서 새롭게 생성된 토큰을 앱에 전달한다.
UserController
@GetMapping("/oauth/callback") public ResponseEntity<?> oauthCallback(@RequestParam("accessToken") String kakaoAccessToken){ System.out.println("스프링에서 받은 카카오토큰 : "+kakaoAccessToken); String blogAccessToken = userService.카카오로그인(kakaoAccessToken); return ResponseEntity.ok().header("Authorization", "Bearer "+blogAccessToken).body(new ApiUtil(null)); }
5. 앱에서 토큰 저장하기
스프링 서버에서 새롭게 받은 토큰을 시큐어 스토리지에 저장한다.
login_page.dart
import 'package:flutter/material.dart'; import 'package:kakao_flutter_sdk/kakao_flutter_sdk.dart'; import 'package:oauthapp/_core/http.dart'; class LoginPage extends StatelessWidget { const LoginPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("카카오 로그인"), ), body: ElevatedButton( child: Text("카카오로그인"), onPressed: () async { kakaoLogin(); }, ), ); } void kakaoLogin() async { try { // 1. 크리덴셜 로그인 - 토큰 받기 OAuthToken token = await UserApi.instance.loginWithKakaoAccount(); // Bl_yCbOpzJe4vQGNCBX_cQI0VVBvdUm3AAAAAQo9dNsAAAGP3IxzfMYNwJ_muSR4 print('카카오계정으로 로그인 성공 ${token.accessToken}'); // 2. 토큰(카카오)을 스프링서버에 전달하기 (스프링 서버한테 나 인증했어!! 라고 알려주는것) final response = await dio.get("/oauth/callback", queryParameters: {"accessToken": token.accessToken}); // 3. 토큰(스프링서버) 응답받기 final blogAccessToken = response.headers["Authorization"]!.first; print("blogAccessToken : ${blogAccessToken}"); // 4. 시큐어 스토리지에 저장 secureStorage.write(key: "blogAccessToken", value: blogAccessToken); } catch (error) { print('카카오계정으로 로그인 실패 $error'); } } }
스프링 서버가 발급받은 토큰을 확인할 수 있다.
회원가입도 완료되었다.
Share article