// 에드센스

1편에서 이어지는 글이다. 카카오 디벨로퍼스에서의 설정은 끝났으니 코드로 남은 구현을 마무리하자.


1. REST API 키 복사하기

여기서 두번째 저것을 사용해야한다. 복사하자.

 

 

 

2. Callback Url을 받을 Controller (인증 코드 요청)

@GetMapping("/user/kakao/callback")
public String kakaoLogin(String code) {
  // code는 카카오 서버로부터 받은 인가 코드
  log.info("kakaoLogin");
  memberService.kakaoLogin(code);
  return "redirect:/";
}

이 컨트롤러에서 다음 그림의 첫번째 한쌍의 절차를 수행한 것이다.

 

 

 

3. 토큰 요청

service의 kakaoLogin 메소드

public void kakaoLogin(String authorizedCode) {
        log.info("kakaoLogin Service 호출");
        // 카카오 OAuth2 를 통해 카카오 사용자 정보 조회
        KakaoUserInfo userInfo = kakaoOAuth2.getUserInfo(authorizedCode);
        System.out.println("userInfo.getNickname() = " + userInfo.getNickname());

        //카카오에서 받아온 사용자의 정보
        Long kakaoId = userInfo.getId();
        String nickname = userInfo.getNickname();
        String email = userInfo.getEmail();

        //카카오 로그인을 통해 이미 회원가입한 회원인지 확인하기 위해 카카오ID를 통해 검색
        MemberVo kakaoMember = memberMapper.findByKakaoId(kakaoId)
                .orElse(null);

        //중복된 사용자가 없다면(처음으로 카카오 로그인을 하는 경우 카카오에서 받은 정보를 통한 회원가입 진행)
        if(kakaoMember == null) {
            MemberVo sameEmailMember = memberMapper.findByEmail(email).orElse(null);
            if(sameEmailMember != null){
                //카카오로그인은 처음인데 이미 그냥 회원가입은 돼있는경우
                kakaoMember = sameEmailMember;
                kakaoMember.setKakao_id(kakaoId);    //이미 저장돼있는 회원 정보에 카카오 ID만 추가해서 다시 저장한다.
                memberMapper.updateMember(kakaoMember);     //기존 회원에 카카오 아이디만 추가해준다
            }
            else{
                String userName = nickname;
                String password = kakaoId + ADMIN_TOKEN;
                String encodedPassword = passwordEncoder.encode(password);
                kakaoMember = MemberVo.builder()
                        .kakao_id(kakaoId)
                        .login_id(email)    //카카오 로그인의 경우에는 login id가 없기에 이메일을 넣어줌
                        .nickname(nickname)
                        .name(userName)
                        .email(email)
                        .login_password(encodedPassword)
                        .build();
                memberMapper.save(kakaoMember); //회원가입

                int idByLoginId = memberMapper.findIdByLoginId(email);
                kakaoMember.setMember_id(idByLoginId);
            }
        }

        // 로그인 처리
        // 스프링 시큐리티 통해 인증된 사용자로 등록
        MemberDetailsImpl memberDetails = new MemberDetailsImpl(kakaoMember);
        Authentication authentication = new UsernamePasswordAuthenticationToken(memberDetails, null, memberDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

 

 

 

kakaoOAuth2 메소드

@Component
public class KakaoOAuth2 {

    public KakaoUserInfo getUserInfo(String authorizedCode) {
        System.out.println("getUserInfo 호출");
        // 인가코드 -> 액세스 토큰
        String accessToken = getAccessToken(authorizedCode);
        // 액세스 토큰 -> 카카오 사용자 정보
        KakaoUserInfo userInfo = getUserInfoByToken(accessToken);

        return userInfo;
    }

    private String getAccessToken(String authorizedCode) {
        System.out.println("getAccessToken 호출");
        // HttpHeader 오브젝트 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HttpBody 오브젝트 생성
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "authorization_code");
        params.add("client_id", "여기에 REST API 키");   
        params.add("redirect_uri", "http://localhost:8080/user/kakao/callback");
        params.add("code", authorizedCode);

        // HttpHeader와 HttpBody를 하나의 오브젝트에 담기
        RestTemplate rt = new RestTemplate();
        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
                new HttpEntity<>(params, headers);

        // Http 요청하기, Post방식으로, 그리고 response 변수의 응답 받음
        ResponseEntity<String> response = rt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                kakaoTokenRequest,
                String.class
        );

        // JSON -> 액세스 토큰 파싱
        String tokenJson = response.getBody();
        JSONObject rjson = new JSONObject(tokenJson);
        String accessToken = rjson.getString("access_token");   //우리가 필요한건 accessToken

        return accessToken;
    }

    //토큰을 통해 사용자 정보 가져오기
    private KakaoUserInfo getUserInfoByToken(String accessToken) {
        // HttpHeader 오브젝트 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HttpHeader와 HttpBody를 하나의 오브젝트에 담기
        RestTemplate rt = new RestTemplate();
        HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(headers);

        // Http 요청하기 - Post방식으로 - 그리고 response 변수의 응답 받음.
        ResponseEntity<String> response = rt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoProfileRequest,
                String.class
        );

        JSONObject body = new JSONObject(response.getBody());
        Long id = body.getLong("id");
        String email = body.getJSONObject("kakao_account").getString("email");
        String nickname = body.getJSONObject("properties").getString("nickname");

        //가져온 사용자 정보를 객체로 만들어서 반환
        return new KakaoUserInfo(id, email, nickname);
    }
}

 

 

 

kakaoLogin 메소드의 마지막 부분인

SecurityContextHolder.getContext().setAuthentication(authentication);

을 통해서 로그인 처리가 된다.

 

*SecurityContextHolder란 Spring Security의 인메모리 세션저장소

 

 

 

4. 로그인 페이지

<button id="login-kakao-btn" onclick="location.href='https://kauth.kakao.com/oauth/authorize?client_id=[여기에RESTAPI키]&redirect_uri=http://localhost:8080/user/kakao/callback&response_type=code'">
        카카오로 로그인하기
</button>

 

 

 

5. 현재 사용자의 정보 가져오기

지금까지 OAuth2를 사용하지 않고 로그인을 구현할 때는 

session.setAttribute("loginMemberId",loginDto.getUserID());

이런식으로 세션에 직접 무언가를 set 해줬는데 스프링 시큐리티를 사용할때는

 

 

MemberDetailsImpl memberDetails = new MemberDetailsImpl(kakaoMember);
Authentication authentication = new UsernamePasswordAuthenticationToken(memberDetails, null, memberDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);

이렇게  MemberDetails로 꺼내 사용자의 session 생성하고 이를 SecurityContextHolder에 저장한다.

이것을 가져올때는

 

 

@GetMapping("/")
public String home(Model model, @AuthenticationPrincipal MemberDetailsImpl memberDetails){
    model.addAttribute("username", memberDetails.getUsername());
    System.out.println("member_id: "+memberDetails.getMemberVo().getMember_id());
    return "index";
}

@AuthenticationPrincipal 어노테이션을 통해 SecurityContextHolder에 저장된 현재 사용자 정보를 가져올 수 있다. 위 코드의 출력 결과는 사용자의 member_id가 된다. 이것을 사용하여 여러 쿼리문의 where에 사용할 수 있겠지? 

 

 

사실 스프링시큐리티와 OAuth2에 대한 정보가 많이 부족하기에 혹시라도 지나가시던 고수님께서 보시고 틀린점이나 추가할 점을 발견하신다면 한소리 부탁드립니다..

 

 

 

 

참고:

+ Recent posts