Oauth2 로그인 구현하기

이수찬·2023년 5월 15일
0

Oauth2 login을 구현해보자!

1. loginService

public JwtTokenDto loginOauth2(LoginOauth2Rq rq) {

        ClientType clientType = rq.getClientType();
        String code = rq.getCode();

        if (KAKAO.equals(clientType)) {
            return oauth2KakaoService.getToken(code);
        }

        if (GOOGLE.equals(clientType)) {
            return oauth2GoogleService.getToken(code);
        }

        throw new AuthenticationException();
    }
  • 요청 정보의 ClientType을 통해 Kakao login인지 Google login인지 판단하여 ClientType이 일치하는 로그인 로직을 수행한다.

2. KakaoService

2-1. Kakao Oauth login 로직

public JwtTokenDto getToken(String code) {

        String kakaoToken = getKakaoToken(code);
        Member member = getMemberByKaKaoToken(kakaoToken);

        // 로그인 로그
        MemberLogInLog memberLogInLog = MemberLogInLog.create(member);
        memberLogInLogRepository.save(memberLogInLog);

        return jwtTokenFactory.generateJwtToken(member);
    }
    1. code를 통해 KakaoAPI에서 accessToken을 가져온다.
    1. accessToken을 통해 KakaoAPI에서 회원정보를 가져온다.
    1. 로그인 log를 찍고, 회원정보를 바탕으로 새로운 토큰을 만들어 클라이언트에 반환한다.

2-2. accessToken 가져오기

private String getKakaoToken(String code) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add(KakaoConstants.CLIENT_ID, clientId);
        params.add(KakaoConstants.REDIRECT_URI, redirectUri);
        params.add(KakaoConstants.GRANT_TYPE, grantType);
        params.add(KakaoConstants.CODE, code);

        HttpEntity<LinkedMultiValueMap<String, String>> httpEntity = new HttpEntity<>(params,
                headers);

        JSONObject jsonObject = getResponse(httpEntity, TOKEN_URL);
        return (String) jsonObject.get(KakaoConstants.ACCESS_TOKEN);
    }
  • KakaoToken을 가져오기 위해서는 해당 API가 요청하는 정보들을 보내줘야한다.
POST /oauth/token HTTP/1.1
Host: kauth.kakao.com
Content-type: application/x-www-form-urlencoded;charset=utf-8

요청 방식
1. 메소드 : POST
2. url : https://kauth.kakao.com/oauth/token

Header
1. contentType : x-www-form-urlencoded

Body(필수)
1. code
2. client_id
3. redirect_uri
4. grant_type

  • Body와 Header에 값을 넣기위해서 Map을 사용해 값을 넣어 httpEntity를 생성한다. (HttpEntity는 Spring이 제공하는 클래스로 HttpHeader와 HttpBody를 모두 가질 수 있다.
  • POST /oauth/token로 httpEntity를 보내 json객체를 가져온후, 해당 객체에서 accessToken을 리턴한다.

private JSONObject getResponse(HttpEntity<LinkedMultiValueMap<String, String>> httpEntity,
                                   String url) {

        try {
            RestTemplate restTemplate = new RestTemplate();
            // 여기서 .postForEntity를 통해 kakao api 로 요청함.
            ResponseEntity<String> response = restTemplate.postForEntity(url, httpEntity,
                    String.class);

            // body에 토큰이 담겨있음/
            String body = response.getBody();
            JSONParser jsonParser = new JSONParser();
            return (JSONObject) jsonParser.parse(body)

        } catch (ParseException e) {

            throw new AuthenticationException();
        }
    }
  • 여기서 KakaoAPI에 요청을 보내기 위해 RestTemplate을 사용했는데, RestTemplate은 스프링3.0이상부터 지원하는 Spring의 HTTP 통신 템플릿으로, 간편하게 Rest방식의 API 호출을 도와준다.
  • PostForEntity메소드를 사용해 해당 url의 API에 HttpEntity를 담아 post방식으로 요청을 보내고 ResponseEntity를 리턴받는다.
  • 그후 응답의 body에서 데이터를 가져와 json객체로 만든 후 반환한다.

2-3. 회원 정보 가져오기

 private Member getMemberByKaKaoToken(String kakaoToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add(AUTHORIZATION, BEARER + kakaoToken);

        HttpEntity<LinkedMultiValueMap<String, String>> httpEntity = new HttpEntity<>(headers);
        JSONObject jsonObject = getResponse(httpEntity, USER_INFO_URL);
        Map<String, Object> attributes = (Map<String, Object>) jsonObject.get(KakaoConstants.KAKAO_ACCOUNT);

        String email = (String) attributes.get("email");

        Member member = findMemberService.findByEmailAndClientTypeOrElseNull(email, KAKAO);
        if (Objects.nonNull(member)) {
            return member;
        }

        JSONObject profile = (JSONObject) attributes.get("profile");
        String nickname = (String) profile.get("nickname");

        member = Member.createCustomer(nickname, email, KAKAO);

        // 회원가입 로그
        memberRepository.save(member);
        MemberSignUpLog memberSignUpLog = MemberSignUpLog.create(member);
        memberSignUpLogRepository.save(memberSignUpLog);

        return member;
    }
GET/POST /v2/user/me HTTP/1.1
Host: kapi.kakao.com
Authorization: Bearer ${ACCESS_TOKEN}/KakaoAK ${APP_ADMIN_KEY}
Content-type: application/x-www-form-urlencoded;charset=utf-8
  • kakaoAPI를 통해 회원정보를 가져오기 위해서는 마찬가지로 해당 API가 요청하는 정보들을 보내줘야한다.

요청 방식
1. 메소드 : POST
2. url : https://kapi.kakao.com/v2/user/me

Header
1. contentType : x-www-form-urlencoded
2. Authorization(필수) : Bearer + ${ACCESS_TOKEN}

  • 로직의 경우 accesstoken을 가져올 때와 동일하게 진행된다.
  • KakaoAPI에서 가져온 email과 ClientType(KAKAO)으로 가입한 회원이 db에 존재하지 않으면, json객체에서 nickname과 email을 뽑아 회원을 생성하고, db에 회원을 저장한 후 회원가입 로그를 남긴다.

2-4. 회원정보를 바탕으로 JWT 생성하기

JwtTokenFactory

public JwtTokenDto generateJwtToken(Member member) {
        Date now = DateUtils.now();
        Date expiredDate = DateUtils.addTime(now, TOKEN_EXPIRE_TIME);
        String token = Jwts.builder()
                .setClaims(createClaims(member))
                .setIssuedAt(now)
                .setExpiration(expiredDate)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .compact();

        LocalDateTime expiredDateTime = LocalDateTime.ofInstant(expiredDate.toInstant(), ZoneId.systemDefault());
        return JwtTokenDto.createJwtTokenDto(token, expiredDateTime);

    }
  • member객체를 통해 jwt를 생성하고, tokenDTO로 변환하여 반환한다.

3.정리

  1. 클라이언트가 Kakao login을 수행한다.
  2. 서버는 KakaoAPI에게 code를 요청한다.
  3. KakaoAPI에게 code정보를 가지고 accessToken을 요청한다.
  4. KakaoAPI에게 accessToken을 가지고 회원정보를 요청한다.
  5. 회원정보를 바탕으로 새로운 토큰을 만들어 클라이언트에게 반환한다.

이렇게 로그인이 완료되면, 다음 요청부터 받은 토큰을 헤더에 담아 요청을 보내 인증을 완료할 수 있다.

아래 글에서 JWT를 통해 인증을 하는지에 대해 설명한다.
https://velog.io/@suzhanlee/JWT-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

0개의 댓글