Spring 심화반 - 2주차 - 3

귀찮Lee·2022년 4월 16일
0

2022년 4월 16일(토)
[스파르타코딩클럽] Spring 심화반 - 2주차 - 3

◎ 소셜 로그인

  • OAuth : 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준
    • 사용자가 애플리케이션에게 모든 권한을 넘기지 않고 사용자 대신 서비스를 이용할 수 있게 해주는 HTTP 기반의 보안 프로토콜
      ex) 카카오, 네이버, 구글, 페이스북...

◎ 카카오 로그인 사용 승인받기 (사이트)

  • 카카오 로그인 사용 승인 받기 기본 구조
  • 내 어플리케이션 > 어플리케이션 추가하기
  • 사이트 도메인 등록하기: 어플리케이션 선택 > 플렛폼 메뉴 선택 > Web 플랫폼 등록 > 사이트 도메인 입력 (http://localhost:8080)
  • 카카오로 로그인 했을 때 인가토큰을 받게 될 Redirect URI (callback) 를 설정
  • 동의 항목 설정 : 프로필 정보, 카카오 계정(이메일) 등을 받을 수 있다.

◎ 카카오 사용자 정보 가져오기 (메뉴얼)

  • 카카오 인가코드 받기 (프론트에서 설정)
https://kauth.kakao.com/oauth/authorize?client_id=본인의 REST API키&redirect_uri={REDIRECT_URI}&response_type=code
  • 인가코드 받을 시 처리
@GetMapping("/user/kakao/callback")
public String kakaoLogin(@RequestParam String code) {
    // authorizedCode: 카카오 서버로부터 받은 인가 코드
    KakaoUserService.kakaoLogin(code); // userService.kakaoLogin 에서 나머지 처리
    return "redirect:/";
}
  • 카카오 사용자 정보 가져오기
  • "인가 코드"로 "액세스 토큰" 요청
public void kakaoLogin(String code) throws JsonProcessingException {
	    // 1. "인가 코드"로 "액세스 토큰" 요청
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP Body 생성
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "authorization_code");
        body.add("client_id", "본인의 REST API키");
        body.add("redirect_uri", "http://localhost:8080/user/kakao/callback");
        body.add("code", code);

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
                new HttpEntity<>(body, headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                kakaoTokenRequest,
                String.class
        );

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);
        String accessToken = jsonNode.get("access_token").asText();
}
  • "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
		// 2. 토큰으로 카카오 API 호출
        // HTTP Header 생성
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoUserInfoRequest = new HttpEntity<>(headers);
        response = rt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoUserInfoRequest,
                String.class
        );

        responseBody = response.getBody();
        jsonNode = objectMapper.readTree(responseBody);
        Long id = jsonNode.get("id").asLong();
        String nickname = jsonNode.get("properties")
                .get("nickname").asText();
        String email = jsonNode.get("kakao_account")
                .get("email").asText();

        System.out.println("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
  • 리팩토링 하는 법 (특정 부분을 함수화하여 넘김)
    • ' "인가 코드"로 "액세스 토큰" 요청 하는 부분 ' 드래그
    • 우클릭 > Refactor > Extract Method (단축키 Ctrl + Alt + M)
    • 꼭 동작이 제대로 작동하는지 확인!
    • ' "액세스 토큰"으로 "카카오 사용자 정보" 가져오기 ' 부분도 똑같이 Extract Method 가능

◎ 카카오 사용자 정보로 회원가입

  • 설계: 사람마다 여러가지로 할 수 있지만, 아래 프로젝트에서는 다음과 같이 실행

    • kakaoId Column 추가 : kakao로 회원가입 한 사람은 "kakaoId" 문자열 추가
    • password는 UUID (랜덤으로 생성한 문자열)을 인코딩하여 생성
    • 기타 나머지 사항은 코드 참고
  • User 테이블에 'kakaoId' 추가 (nullable true, unique true)

  • 회원 가입 : kakaoId 를 가진 회원이 없는 경우에만 회원 가입

				// DB 에 중복된 Kakao Id 가 있는지 확인
        Long kakaoId = kakaoUserInfo.getId();
        User kakaoUser = userRepository.findByKakaoId(kakaoId)
                .orElse(null); // UserRepository 설정 해주어야 함
        if (kakaoUser == null) {
            // 회원가입
            // username: kakao nickname
            String nickname = kakaoUserInfo.getNickname();

            // password: random UUID
            String password = UUID.randomUUID().toString();
            String encodedPassword = passwordEncoder.encode(password);

            // email: kakao email
            String email = kakaoUserInfo.getEmail();
            // role: 일반 사용자
            UserRoleEnum role = UserRoleEnum.USER;

            kakaoUser = new User(nickname, encodedPassword, email, role, kakaoId);
            userRepository.save(kakaoUser);
        }
  • 강제 로그인 처리
    • "로그인 성공 사용자 정보" (UserDetails) 는 SecurityContext 에 저장됨
    • SecurityContextHolder 를 통해 SecurityContext 에 "로그인 성공 사용자 정보" 직접 추가
    • 구현
          // 4. 강제 로그인 처리
          UserDetails userDetails = new UserDetailsImpl(kakaoUser);
          Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
          SecurityContextHolder.getContext().setAuthentication(authentication);
  • 이후 기능별로 리팩토링시, 용이하게 사용할 수 있다.
    • KakaoUserService, KakaoUserInfoDto 등으로 파일 분리하는 것도 좋다.
profile
배운 것은 기록하자! / 오류 지적은 언제나 환영!

0개의 댓글