목적
: 이번 페이지는 카카오로그인 구현시, 프론트엔드와 플로우를 맞춰서 작성한 코드
* 프론트와 맞추기 전, 백엔드단에서 구현하며 확인하기 위한 코드는 아래 1번글 참고
* 몇가지 수정사항을 제외하고는 1번글과 큰 차이가 없음
[Oauth2.0] 카카오 로그인 (백엔드 테스팅 버젼) : https://radpro.tistory.com/517
사전 준비
: 카카오 Developer에서 앱 등록 (https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite)
: 카카오 Developer에서 필요한 profile 및 동의 항목 설정
: 내 애플리케이션에서 플랫폼 -> Web에 도메인 URI 등록하기
: 카카오 로그인에서 Redirect URI 등록해두기
* APP Key는 App Id가 아닌 REST API 키를 사용하기
플로우
1. 프론트에서 리다이렉트 URI 경로를 받기 위한 백엔드 API 호출 ( /kakao/oauth )
*이때, 반드시 백엔드에선 http://localhost:3000 과 같이 www가 없는 경로로 cors 등록필요(안그럼 cors뜸)
2. 백엔드에서 프론트로 전달한 URI로 프론트가 리다이렉트 => 인가 코드를 받기 위한 페이지 선정 필요
3. 인가 코드를 code라는 키값으로 백엔드로 전달하는 API 호출 ( /kakao/callback )
*이때, 반드시 백엔드에선 http://www.localhost:3000 과 같이 www가 있는 경로로 cors 등록필요(안그럼 cors뜸)
*카카오에 등록한 www가 붙은 방식의 리다이렉트 경로가 이때 사용되는 것임
4. 백엔드에서 code라는 파라미터로 전달받은 인가 코드를 이용해 카카오 서버에 토큰 발급 요청
5. 백엔드에서 발급받은 카카오 토큰으로 카카오 서버에 카카오 계정정보 요청 => 이때 필요한 것이 카카오 유저 동의 항목
6. 백엔드는 전달받은 유저정보로 자체 계정 생성 및 자체 토큰 발급하여 프론트로 전달 (로그인 완료)
구현 코드
1. KakaoToken
package TeamBigDipper.UYouBooDan.global.oauth2.kakao;
import lombok.Data;
@Data
public class KakaoToken {
private String access_token;
private String token_type;
private String refresh_token;
private int expires_in;
private String Scope;
private int refresh_token_expires_in;
}
2. KakaoProfile
package TeamBigDipper.UYouBooDan.global.oauth2.kakao;
import lombok.Data;
@Data
public class KakaoProfile {
private Long id;
private String connected_at;
private Properties properties;
private KakaoAccount kakao_account;
@Data
public class Properties {
private String nickname;
public String profile_image;
public String thumbnail_image;
}
@Data
public class KakaoAccount {
private Boolean profile_needs_agreement;
private Boolean profile_nickname_needs_agreement;
private Profile profile;
private Boolean has_email;
private Boolean email_needs_agreement;
public Boolean is_email_valid;
public Boolean is_email_verified;
public String email;
@Data
public class Profile {
private String nickname;
public String thumbnail_image_url;
public String Profile_image_url;
}
}
}
3. KakaoOauthController
package TeamBigDipper.UYouBooDan.global.oauth2.kakao;
import TeamBigDipper.UYouBooDan.global.security.jwt.JwtTokenizer;
import TeamBigDipper.UYouBooDan.member.entity.Member;
import TeamBigDipper.UYouBooDan.member.service.MemberService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletResponse;
@RequiredArgsConstructor
@RestController
@RequestMapping("/kakao")
public class KakaoOauthController {
@Getter
@Value("${oauth.kakao.appKey.restApiKey}")
private String kakaoAppKey;
@Getter
@Value("${oauth.kakao.clientId}")
private String kakaoClientId;
private final MemberService memberService;
private final JwtTokenizer jwtTokenizer;
/**
* 프론트 요청 API : 인증 code 받기용
* @return redirect url for kakao Authorization
*/
@GetMapping("/oauth")
public ResponseEntity<?> kakaoConnect() {
StringBuffer url = new StringBuffer();
url.append("https://kauth.kakao.com/oauth/authorize?");
url.append("client_id=" + getKakaoAppKey()); // App Key
url.append("&redirect_uri=http://www.localhost:3000/auth/kakaoredirect"); // 프론트쪽에서 인가 코드를 받을 리다이렉트 URL(카카오 리다이렉트에 등록 필요)
url.append("&response_type=code");
return new ResponseEntity<>(url.toString(), HttpStatus.OK); // 프론트 브라우저로 보내는 주소(프론트에서 받아서 리다이렉트 시키면, 인가코드를 받을 수 있다.)
}
/**
* 카카오 callback API : 토큰 발급 및 서비스 멤버 생성
* @param code 카카오 인증 code (프론트에서 카카오로부터 받아서 이 API에 담아서 전달해주면 됨)
* @return Success Login message
* @throws JsonProcessingException
*/
@GetMapping("/callback")
public String kakaoLogin(@RequestParam("code") String code, HttpServletResponse response) throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders kakaoTokenReqHeaders = new HttpHeaders(); // springFramework.http 라이브러리
kakaoTokenReqHeaders.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); // json이 아니다 (카카오 REST API 참고)
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", getKakaoAppKey());
params.add("redirect_url", "http://localhost:8080"); // redirect url 확인하기
params.add("code", code);
HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
new HttpEntity<>(params, kakaoTokenReqHeaders);
// fetching for token
ResponseEntity<String> oauthTokenResponse = restTemplate.exchange(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
kakaoTokenRequest,
String.class
);
// kakao token converting process
ObjectMapper objectMapper = new ObjectMapper();
KakaoTokenVo kakaoToken = null;
try { kakaoToken = objectMapper.readValue(oauthTokenResponse.getBody(), KakaoTokenVo.class); }
catch (JsonMappingException je) { je.printStackTrace(); }
RestTemplate restTemplate2 = new RestTemplate();
HttpHeaders userDetailsReqHeaders = new HttpHeaders();
userDetailsReqHeaders.add("Authorization", "Bearer " + kakaoToken.getAccess_token());
userDetailsReqHeaders.add("Content-type", "application/x-www-form-urlencoded;charset=UTF-8");
HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(userDetailsReqHeaders);
// fetching for profile data
ResponseEntity<String> userDetailsResponse = restTemplate2.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
kakaoProfileRequest,
String.class
);
// kakao profile converting process
ObjectMapper objectMapper2 = new ObjectMapper();
KakaoProfileVo kakaoProfile = null;
try { kakaoProfile = objectMapper2.readValue(userDetailsResponse.getBody(), KakaoProfileVo.class); }
catch (JsonMappingException je) { je.printStackTrace(); }
// 서비스 회원 등록 위임
Member kakaoMember = memberService.createKakaoMember(kakaoProfile, kakaoToken.getAccess_token());
// 시큐리티 영역
// Authentication 을 Security Context Holder 에 저장
Authentication authentication = new UsernamePasswordAuthenticationToken(kakaoMember.getEmail(), kakaoMember.getPassword()); // password 확인
SecurityContextHolder.getContext().setAuthentication(authentication);
// 자체 JWT 생성 및 HttpServletResponse 의 Header 에 저장 (클라이언트 응답용)
String accessToken = jwtTokenizer.delegateAccessToken(kakaoMember);
String refreshToken = jwtTokenizer.delegateRefreshToken(kakaoMember);
response.setHeader("Authorization", "Bearer " + accessToken);
response.setHeader("RefreshToken", refreshToken);
return "Success Login: User";
}
}
4. MemberService 내 kakaoMember 생성 메소드 (자체 서비스 이용을 위함)
@Transactional
public Member createKakaoMember (KakaoProfileVo kakaoProfile, String accessToken) {
// 중복 가입 방지 로직 추가
Optional<Member> optMember;
if(kakaoProfile.getKakao_account().getEmail()==null) optMember = memberRepository.findByEmail(kakaoProfile.getId().toString()+"@uyouboodan.com");
else optMember = memberRepository.findByEmail(kakaoProfile.getKakao_account().getEmail());
if(optMember.isEmpty()) {
Member member = Member.builder()
.memberId(kakaoProfile.getId())
.nickname(new Name("Mock"+ kakaoProfile.getId()))
.password(passwordEncoder.encode(getInitialKey())) // yml을 통해 시스템 변수 default값 설정해둠
.oauthId(kakaoProfile.getId())
.oauthAccessToken(accessToken)
.memberStatus(Member.MemberStatus.MEMBER_ACTIVE)
.build();
if (kakaoProfile.getKakao_account().getEmail()==null) member.modifyEmail(kakaoProfile.getId().toString()+"@uyouboodan.com"); // email 수집 미동의시, 자사 email로 가입됨
else member.modifyEmail(kakaoProfile.getKakao_account().getEmail());
member.defaultProfile();
List<String> roles = customAuthorityUtils.createRoles(member.getEmail());
member.setRoles(roles);
return memberRepository.save(member);
}
else {
Member member = optMember.get();
member.modifyOauthToken(accessToken);
return memberRepository.save(member);
}
}
5. application.yml
oauth:
kakao:
appKey:
restApiKey: 카카오_RestApiKey
clientId: 카카오_ClientID
initialKey: 서비스_회원_등록시_초기_설정_임시_비밀번호
'Java & Spring > Spring' 카테고리의 다른 글
| [OAuth2.0] Google 로그인 (백엔드 테스트 버젼) (2) | 2023.02.06 |
|---|---|
| [OAuth2.0] 이론 (0) | 2023.02.06 |
| [Spring] 비밀번호 검증 및 수정 로직 (0) | 2023.01.31 |
| [OAuth2.0] 카카오 로그인 (백엔드 테스팅 버젼) (0) | 2023.01.30 |
| [Spring] SpringSecurity - JwtTokenizer (1) | 2023.01.26 |




