[Spring Boot] 스프링 부트로 카카오 간편 로그인 API 구현

Dawon Ruby Choi·2024년 2월 12일
post-thumbnail

로그인 화면

  • 화면은 BootStrap으로 구현하였습니다.
  • 카카오로 간편 로그인 버튼을 누르면 카카오 서버로 이동합니다.

카카오 로그인 api 연동

kakao developers 설정

링크 : https://developers.kakao.com/

1. 프로젝트 등록

회원가입을 하고 애플리케이션에 들어가 프로젝트를 추가해줍니다.
테스트는 앱 권한 신청을 할 필요 없이 url 등록으로 간편 로그인을 구현할 수 있습니다.

2. 플랫폼 등록

사이트 도메인을 등록해줍니다. 저는 로컬 환경에서 테스트 예정이기 때문에 해당 주소(http://localhost:8080) 로 설정했습니다. 그리고 아래 RdirectUrl 등록 하러 가기를 클릭해줍니다.

사용자가 카카오 로그인 후 Redirect로 이동할 Url을 입력해주면 됩니다.

3. 동의항목 선택

동의 항목을 선택합니다. 프로젝트에 맞게 수집하는 고객의 정보를 설정해주면 됩니다.
저희는 사용자의 프로필사진과 배송지 정보도 받고 있기 때문에 해당 항목도 필수로 설정해주었습니다.

4. 앱키 확인

위 세과정을 완료했다면 아래처럼 api에 필요한 key들이 생성됩니다.

SpringBoot에 적용하기

저희는 REST API 키 값을 사용하였습니다. 적용하기에 앞서 아래 카카오가 제공하는 REST API 문서를 읽고 오시면 아래 코드를 이해하는데 더 도움이 되실 것 같습니다.

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

index.html

<div>
	<a class="btn btn-outline-dark mb-3" href="https://kauth.kakao.com/oauth/authorize?client_id=REST API 키 값&redirect_uri=http://localhost:8080/kakaoLogin&response_type=code" style="width:100%">카카오로 간편 로그인</a>
</div>

👾client_id= 뒤에 REST API 키 값을 넣어줍니다
👾redirect_uri= 뒤에 사용자가 카카오 로그인 후 Redirect로 이동할 Url을 넣어줍니다.

Member.java

public class Member {
	private String mbId;
	private String mbPwd;
	private String mbName;
	private String mbPhoto;
	private String mbIntro;
	private String mbNickName;
	private String mbEmail;
	@DateTimeFormat(pattern = "yyyy-MM-dd")
	private Date mbBirth;
	private String mbTel;
	private int mbPoint;
	private String mbStatus;
	private String isAdmin;
	private String isPrivate;
	private Date nextChange;
	private String isKakao;

	private String isBanned;
	
}

AccountController.java

@SessionAttributes("loginUser")
@Controller
public class AccountController {

@Autowired
	private KakaoLoginService kService;

@GetMapping("kakaoLogin")
    public String kakaoLogin(@RequestParam(value="code", required=false) String code, Model model) {
    	System.out.println(code);
    	String accessToken = kService.getAccessToken(code);
    	System.out.println("###access_Token#### : " + accessToken);
		// 위의 access_Token 받는 걸 확인한 후에 밑에 진행
    	
		HashMap<String, Object> userInfo = kService.getUserInfo(accessToken);
		System.out.println("###id#### : " + userInfo.get("id"));
		System.out.println("###name#### : " + userInfo.get("name"));
		System.out.println("###profileImage#### : " + userInfo.get("profileImage"));
		System.out.println("###birthday#### : " + userInfo.get("birthday"));
		System.out.println("###birthyear#### : " + userInfo.get("birthyear"));
		System.out.println("###phoneNumber#### : " + userInfo.get("phoneNumber"));
		System.out.println("###email#### : " + userInfo.get("email"));
    			
		try {
	    	String mbId = (String)userInfo.get("id") + "kAkAo"; //카카오 로그인 시 자동으로 ID뒤에 붙일 문자열
	    	String name = (String)userInfo.get("name");
	    	String profileImage = (String)userInfo.get("profileImage");
	    	String email = (String)userInfo.get("email");
	    	SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
	    	Date date;
			date = sdf.parse((String)userInfo.get("birthyear") + (String)userInfo.get("birthday"));
			
	    	String phoneNumber = (String)userInfo.get("phoneNumber");
	    	String phone = "0" + phoneNumber.substring(4).replace("-", ""); //DB 선택 사항
	    	
            //사용자가 이미 회원가입한 회원인지 확인
	    	Member m;
	    	int result = aService.idCheck(mbId);
	    	System.out.println(mbId);
	    	System.out.println(name);
	    	System.out.println(profileImage);
	    	System.out.println(date);
	    	System.out.println(phone);
	    	int result2;
	    	
            
	    	if (result > 0) { //이미 가입한 경우 회원 정보를 가져옴
	    		m = new Member();
	    		m.setMbId(mbId);
	    	} else {
	    		m = new Member();
	    		m.setMbId(mbId);
	    		m.setMbPwd(bcrypt.encode(mbId));
	    		m.setMbName(name);
	    		m.setMbPhoto(profileImage);
	    		m.setMbNickName(mbId);
	    		m.setMbBirth(date);
	    		m.setMbTel(phone);
	    		m.setMbEmail(email);
	    		m.setIsKakao("Y");
	    		result2 = aService.signUpMember(m);
	    	}
	    	m = aService.login(m);
	    	model.addAttribute("loginUser", m); //로그인한 사용자 정보를 뷰로 전달
		} catch (ParseException e) {
			e.printStackTrace();
		}
		
    	return "redirect:/"; //메인 페이지로 이동
    	
    }

}

👾 getAccessToken(code)를 호출하여 카카오로부터 액세스 토큰을 받아오고 이 토큰은 카카오 API를 호출하는데 사용됩니다.
👾 getUserInfo(accessToken)를 호출하여 DB에 필요한 사용자의 카카오 프로필 정보를 가져옵니다.

kakaoLoginService.java

public interface KakaoLoginService {

	String getAccessToken(String code);

	HashMap<String, Object> getUserInfo(String accessToken);

}

kakaoLoginServiceImpl.java

@Service
public class KakaoLoginServiceImpl implements KakaoLoginService{

	@Override
	public String getAccessToken(String code) {
		String access_Token = "";
		String refresh_Token = "";
		String reqURL = "https://kauth.kakao.com/oauth/token";
		
		URL url;
		try {
			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="); //💡REST_API키 본인이 발급받은 key 넣어주기
			sb.append("&redirect_uri="); //💡REDIRECT_URI 본인이 설정한 주소 넣어주기
			
			sb.append("&code=" + code);
			bw.write(sb.toString());
			bw.flush();
			
			// 결과 코드가 200이라면 성공
			int responseCode = conn.getResponseCode();
			System.out.println("responseCode : " + responseCode);
			
			// 요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
			BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String line = "";
			String result = "";
			
			while ((line = br.readLine()) != null) {
				result += line;
			}
			System.out.println("response body : " + result);

			// jackson objectmapper 객체 생성
			ObjectMapper objectMapper = new ObjectMapper();
			// JSON String -> Map
			Map<String, Object> jsonMap = objectMapper.readValue(result, new TypeReference<Map<String, Object>>() {
			});
			
			access_Token = jsonMap.get("access_token").toString();
			refresh_Token = jsonMap.get("refresh_token").toString();

			System.out.println("access_token : " + access_Token);
			System.out.println("refresh_token : " + refresh_Token);

			br.close();
			bw.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
				
		return access_Token;
	}

	@Override
	public HashMap<String, Object> getUserInfo(String accessToken) {
		// 요청하는 클라이언트마다 가진 정보가 다를 수 있기에 HashMap타입으로 선언
		HashMap<String, Object> userInfo = new HashMap<String, Object>();
		
		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 " + accessToken);
			
			int responseCode = conn.getResponseCode();
			System.out.println("responseCode : " + responseCode);
			
			BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

			String line = "";
			String result = "";
			
			while ((line = br.readLine()) != null) {
				result += line;
			}
			
			System.out.println("response body : " + result);
			System.out.println("result type" + result.getClass().getName()); // java.lang.String
			
			// jackson objectmapper 객체 생성
			ObjectMapper objectMapper = new ObjectMapper();
			// JSON String -> Map
			Map<String, Object> jsonMap = objectMapper.readValue(result, new TypeReference<Map<String, Object>>() {
			});
			
			System.out.println(jsonMap.get("properties"));

			Map<String, Object> properties = (Map<String, Object>) jsonMap.get("properties");
			Map<String, Object> kakao_account = (Map<String, Object>) jsonMap.get("kakao_account");
			
			// System.out.println(properties.get("nickname"));
			// System.out.println(kakao_account.get("email"));
			
			String id = jsonMap.get("id").toString();
			String name = kakao_account.get("name").toString();
			String profileImage = properties.get("profile_image").toString();
			String email = kakao_account.get("email").toString();
			String birthday = kakao_account.get("birthday").toString();
			String birthyear = kakao_account.get("birthyear").toString();
			String phoneNumber = kakao_account.get("phone_number").toString();
			
			userInfo.put("id", id);
			userInfo.put("name", name);
			userInfo.put("profileImage", profileImage);
			userInfo.put("email", email);
			userInfo.put("birthday", birthday);
			userInfo.put("birthyear", birthyear);
			userInfo.put("phoneNumber", phoneNumber);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return userInfo;
	}

}

카카오 API 과정

인증 (신원 확인을 하는 과정)

  1. 인가코드 받기 요청합니다. (client > 카카오 server)

인가 (접근권한을 얻는 일)

  1. 사용자가 로그인 후 인가를 위해 카카오 서버에서 우리 client로 "동의 화면"을 넘겨줍니다.
  2. 유저가 동의를 누르면 개발자가 제출한 redirectURL 쪽으로 카카오서버가 인가코드를 보내옵니다.

토큰 받기

  1. 토큰받기까지 해야 로그인이 완료되며, POST로 정보,code,grantType,clientId,redirectURL을 URL 형태로 만들고 송신합니다.
  2. response로 access_token, id_token 등을 JSON 형태로 받을 수 있습니다.

유저 정보 받기

  1. response로 id값과 kakao_account라는 유저 정보 객체를 받아올 수 있습니다.

참고 블로그 : https://velog.io/@adguy/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84web%EC%9A%B0%EB%A6%AC%EB%82%98%EB%9D%BC%EC%97%90%EC%84%9C-%EA%B0%80%EC%9E%A5-%EC%89%BD%EA%B2%8C-%EC%95%8C%EB%A0%A4%EC%A3%BC%EA%B8%B0React-restAPI-%EB%B0%A9%EC%8B%9D

profile
나의 코딩 다이어리🖥️👾✨

0개의 댓글