본문 바로가기

Spring

[Spring] Spring Security + 카카오 OAuth2 로그인 구현

728x90
반응형
SMALL

 

카카오 로그인 동작 구현 시연 영상

 

카카오 OAuth2 로그인 Setting

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

  • 위 링크는 카카오 로그인 공식문서
  • 카카오 OAuth2 로그인의 흐름 및 카카오 로그인을 구현할 때 필수적으로 지켜야할 규약 등을 볼 수 있다.

API Key 받기

 

카카오계정

 

accounts.kakao.com

  • 카카오 로그인 후 애플리케이션을 추가해준다.

카카오 디벨로퍼스 내 애플리케이션 화면

  • 애플리케이션 추가하기 선택

작성할 페이지 팝업

  • 앱 이름, 사업자 명 을 작성하고  카테고리는 "교육" 으로 체크박스에 동의하고 저장
  • 등록한 애플리케이션을 선택

생성한 파일의 API 키들

 

로그인 활성화

사이드 메뉴바에서 카카오 로그인을 눌렀을 때 화면

  • 이 화면에서 활성화 설정을 ON 해준다.

동의 항목 설정

  • 메뉴바에서 카카오 로그인 밑에 동의 항목으로 이동

 

동의 항목 페이지

  • 개인정보 동의 항목 심사 신청을 신청하지 않으면 받을 수 있는 사용자의 정보는 닉네임과 프로필 사진 뿐이다.
  •  설정을 눌러 동의 해주면 된다.

설정을 누르면 나오는 모달 창

  • 여기서 필수 등록, 선택 동의 이용 중 동의 등을 할 수 있고 동의 목적을 필수 적으로 작성해주어야 한다.
  • 본인은 "학습용" 이라고 작성하였음
  • 상태가 바뀐걸 확인 할 수 있음

 

  • 그 다음 메뉴바에서 플랫폼을 선택 하고 이동

플랫폼 화면

  • 해당 페이지에서 Web플랫폼 등록 선택

사이트 도메인 설정 하는 모달 창

  • 로컬에서 돌린다면 localhost로 지정
  • 혹은 127.0.0.1로 지정
  • 배포하는 도메인이 있다면 도메인을 작성

RedirectURL 설정

  • 로그인 요청이 들어와서 승인 후 Redirect 될 주소를 적어주어야 한다.
  • 아까 로그인 활성화를 했던 페이지로 이동 (메뉴바에서 로그인)
  • 맨 밑으로 내리면 RedirectURL 등록 버튼

리다이렉트할 주소 작성

  • 본인은 http://localhost:8080/oauth2/kakao/callback 로 설정 해 주었고
  • 만약 https로 배포하였다면 https로 적어주어야 한다.


카카오 OAuth2 로그인 To Spring

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

application.yml

application.yml 파일 세팅

Controller

@Controller
@RequestMapping("/oauth2")
public class OAuth2Controller {

    // kakao 로그인
    @Value("${app.oauth2.kakao.client-id}")
    private String kakaoClientId;
    @Value("${app.oauth2.kakao.redirect-uri}")
    private String kakaoRedirectUri;
    @Value("${app.oauth2.kakao.token-uri}")
    private String kakaoTokenUri;
    @Value("${app.oauth2.kakao.user-info-uri}")
    private String kakaoUserInfoUri;

    @Value("${app.oauth2.password}")
    private String oauth2Password;

    @Autowired
    private UserService userService;

    @Autowired
    private AuthenticationManager authenticationManager;

    // Kakao 인증 처리 callback
    @GetMapping("/kakao/callback")
    public String kakaoCallBack(String code, Model model){   // Kakao 가 보내준 code 값 받아오기
        //------------------------------------------------------------------
        // ■ code 값 확인
        //   code 값을 받았다는 것은 인증 완료 되었다는 뜻..
        //System.out.println("\n<<카카오 인증 완료>>\ncode: " + code);

        //----------------------------------------------------------------------
        // ■ Access token 받아오기 <= code 값 사용
        // 이 Access token 을 사용하여  Kakao resource server 에 있는 사용자 정보를 받아오기 위함.
        KakaoOAuthToken token = kakaoAccessToken(code);

        //------------------------------------------------------------------
        // ■ 사용자 정보 요청 <= Access Token 사용
        KakaoProfile profile = kakaoUserInfo(token.getAccess_token());

        //---------------------------------------------------
        // ■ 회원가입 시키기  <= KakaoProfile (사용자 정보) 사용 => user 객체 전달하기
        //User kakaoUser = registerKakaoUser(profile);

        //---------------------------------------------------
        // ■ 로그인 처리
        //loginKakaoUser(kakaoUser);

        //기회원자인지 확인
        User kakaoUser = kakaoUserTemp(profile);
        if(kakaoUser != null){  //회원 정보가 없을 경우
            model.addAttribute("info", kakaoUser);
            return "user/login";
        }

        return "redirect:/";
    }

    @PostMapping("/register")
    public String registerOk(@ModelAttribute User kakaoUser, Model model){
       /* System.out.println("""
               [카카오 인증 회원 정보]
                 username: %s
                 name: %s
                 password: %s  
                 provider: %s
                 providerId: %s
                 
               """.formatted(kakaoUser.getUsername(), kakaoUser.getName(), kakaoUser.getPassword(), kakaoUser.getProvider(), kakaoUser.getProviderId()));*/
        int cnt = userService.register(kakaoUser);  // 회원 가입!
        User user = null;
        if(cnt > 0){
            //System.out.println("[Kakao 인증 회원 가입 성공]");
            user = userService.findByUsername(kakaoUser.getUsername());
        } else {
            //System.out.println("[Kakao 인증 회원 가입 실패]");
        }
        loginKakaoUser(user);       //로그인

        return "redirect:/";
    }




    // 로그인 진행
    private void loginKakaoUser(User kakaoUser) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                kakaoUser.getUsername(),
                oauth2Password
        );

        Authentication authentication = authenticationManager.authenticate(authenticationToken);

        SecurityContext sc = SecurityContextHolder.getContext();
        sc.setAuthentication(authentication);

        U.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sc);

        //System.out.println("Kakao 인증 로그인 처리 완료");
    }



    //기회원자인지 확인하기 위한 User찾기
    private User kakaoUserTemp(KakaoProfile profile) {
        String provider = "KAKAO";
        String providerId = "" + profile.getId();
        String username = provider + "_" + providerId;
        String name = profile.getKakaoAccount().getProfile().getNickname();
        String password = oauth2Password;

        User user = userService.findByUsername(username);
        User newUser = null;
        if (user == null) {   // 미가입자 인 경우 회원 가입 진행
            newUser = User.builder()
                    .username(username)
                    .name(name)
                    .password(password)
                    .provider(provider)
                    .providerId(providerId)
                    .build();
        } else {
            loginKakaoUser(user);
        }
        return newUser;
    }
    //


    //-----------------------------------------------------------------------------
    // 회원가입 시키기  (username, password, name 필요)
    // Kakao 로그인 한 회원을 User 에 등록하기
    private User registerKakaoUser(KakaoProfile profile) {
        // 새로이 가입시킬 username 을 생성 (unique 해야 한다!)
        String provider = "KAKAO";
        String providerId = "" + profile.getId();
        String username = provider + "_" + providerId;
        String name = profile.getKakaoAccount().getProfile().getNickname();
        String password = oauth2Password;


        /*System.out.println("""
               [카카오 인증 회원 정보]
                 username: %s
                 name: %s
                 password: %s  
                 provider: %s
                 providerId: %s
                 
               """.formatted(username, name, password, provider, providerId));*/

        // 회원 가입 진행하기 전에
        // 이미 가입한 회원인지, 혹은 비가입자인지 체크하여야 한다
        User user = userService.findByUsername(username);
        if(user == null){   // 미가입자 인 경우 회원 가입 진행
            User newUser = User.builder()
                    .username(username)
                    .name(name)
                    .password(password)
                    .provider(provider)
                    .providerId(providerId)
                    .build();

            int cnt = userService.register(newUser);  // 회원 가입!
            if(cnt > 0){
                //System.out.println("[Kakao 인증 회원 가입 성공]");
                user = userService.findByUsername(username);  // 다시 읽어오기,  regDate 정보
            } else {
                //System.out.println("[Kakao 인증 회원 가입 실패]");
            }

        } else {
            //System.out.println("[Kakao 인증. 이미 가입된 회원입니다]");
        }

        return user;
    }  // end registerKakaoUser()


    // Kakao 사용자 정보 요청하기
    private KakaoProfile kakaoUserInfo(String accessToken) {
        RestTemplate rt = new RestTemplate();

        // header  준비
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // body 는 필요없다.  위 header 만 담은 HttpEntity 생성
        HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest =
                new HttpEntity<>(headers);

        // 요청!
        ResponseEntity<String> response = rt.exchange(
                kakaoUserInfoUri,
                HttpMethod.POST,
                kakaoProfileRequest,
                String.class
        );
        //System.out.println("카카오 사용자 Profile 요청 응답: " + response);
        //System.out.println("카카오 사용자 Profile 응답 : " + response.getBody());

        // 사용자 정보 JSON -> Java 로 받아내기
        ObjectMapper mapper = new ObjectMapper();
        KakaoProfile profile = null;
        try {
            profile = mapper.readValue(response.getBody(), KakaoProfile.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        // 확인
        /*System.out.println("""
                [카카오 회원정보]
                 id: %s
                 nickname: %s
                """.formatted(profile.getId(), profile.getKakaoAccount().getProfile().getNickname()));*/

        return profile;
    }

    // Kakao Access Token 받아오기
    public KakaoOAuthToken kakaoAccessToken(String code){
        // POST 방식으로 key-value 형식으로 데이터 요청 (카카오 서버 쪽으로!)
        RestTemplate rt = new RestTemplate();

        // header 준비 (HttpHeader)
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // body 데이터 준비 (HttpBody)
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "authorization_code");
        params.add("client_id", kakaoClientId);
        params.add("redirect_uri", kakaoRedirectUri);
        params.add("code", code);   // 인증 직후 받은 code 값 사용!

        // 위 header 와 body 를 담은 HttpEntity 생성
        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
                new HttpEntity<>(params, headers);

        // 요청!
        ResponseEntity<String> response = rt.exchange(
                kakaoTokenUri,   // Access Token 요청 uri
                HttpMethod.POST,  // request method
                kakaoTokenRequest,  // HttpEntity (body + header)
                String.class   // 응답받을 타입
        );
       // System.out.println("카카오 AccessToken 요청 응답: " + response);
        //System.out.println("카카오 AccessToken 응답 body: " + response.getBody());

        // 응답받은 Json -> Java Object
        ObjectMapper mapper = new ObjectMapper();
        KakaoOAuthToken token = null;

        try {
            token = mapper.readValue(response.getBody(), KakaoOAuthToken.class);
            // 확인
            //System.out.println("카카오 AccessToken: " + token.getAccess_token());
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        return token;
    }

} // end Controller
  • 해당 Controller는 내가 지정한 CallBack 함수에 대해 카카오 서버로 부터 Token을 받고 회원 가입을 하거나 로그인을 할 수 있도록 구상 및 구현

유저의 도메인, 서비스, Repository 등 의 로그인 동작 구현 및 구상 관련 내용은

 https://jk25.tistory.com/175 

 

[Spring] Naver OAuth2 로그인 + Spring Security + Naver 검색 API 활용

외부 로그인 (OAuth2)를 프로젝트에 활용해보기 위해서 NAVER OAuth2 로그인을 구현해 보고자 하였다. 프로젝트 내 네이버 로그인 동작 Naver OAuth2 로그인 설정하기 https://developers.naver.com/apps/#/register 애

jk25.tistory.com

아래 링크를 통해 확인

728x90
반응형
LIST