동작흐름
1. 로그인 인증코드 요청
2. 인증코드 전달
3. 인증코드 전송(AccessToken)
4. 인증코드로 토큰 요청
5. AccessToken발급, 사용자 정보 발급(이메일, 프로필사진, 닉네임 등)
6. 카카오에서 발급받은 토큰으로 서버전용 토큰 생성(JWT Token)
7. 카카오 정보가 담긴 Jwt 토큰을 프론트에 전달
[ 4, 5, 6 ] 인가 토큰으로 카카오 서버에서 사용자 확인 후, DB에 User 저장, 전용 토큰 발행
- 클라이언트로부터 인가 코드를 받는다.
- 받은 인가 코드로 카카오 서버에 엑세스 토큰을 요청한다.
(이 때 Redirect URI는 프론트와 같아야 한다. 프론트 쪽에 맞추면 된다!) - 발급받은 엑세스 토큰으로 카카오 서버에 유저 정보를 받아온다.
- 필요 시에 회원 가입을 진행한다.
- DB에 없을 경우 : User 새로 생성 후 DB에 저장
- DB에 이미 존재할 경우 : 다음 단계로 넘어가기 - 우리 사이트 전용 JWT 토큰을 발행한다.
KakaoOauth2.java
@Component
public class KakaoOAuth2 {
public KakaoUserInfo getUserInfo(String authorizedCode) {
// 1. 인가코드 -> 액세스 토큰 (사진의 3번 과정)
String accessToken = getAccessToken(authorizedCode);
// 2. 액세스 토큰 -> 카카오 사용자 정보 (사진의 4,5번 과정)
KakaoUserInfo userInfo = getUserInfoByToken(accessToken);
return userInfo;
}
private String getAccessToken(String authorizedCode) {
// Http Header 생성
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", "서버와 맞춘 id");
params.add("code", authorizedCode);
//카카오에 요청을 보낼 때,
//https://kauth.kakao.com/oauth/authorize?client_id=b631c07985004c604dbf87eed681b185&redirect_uri=http://localhost:8080/user/kakao/callback&response_type=code
//에서의 redirect_uri를 의미합니다. 보낸것과, 여기서 사용하는것이 같아야합니다.
params.add("redirect_uri", "http://meetball.shop/user/kakao/callback");
// HttpHeader와 HttpBody를 하나의 오브젝트에 담기
RestTemplate rt = new RestTemplate();
rt.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
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
);
// HTTp응답(JSON) -> 액세스 토큰 파싱
String tokenJson = response.getBody();
JSONObject rjson = new JSONObject(tokenJson);
String accessToken = rjson.getString("access_token");
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();
rt.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
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");
String picture = "";
if(!body.getJSONObject("kakao_account").getJSONObject("profile").getBoolean("is_default_image")){
picture = body.getJSONObject("kakao_account").getJSONObject("profile").getString("profile_image_url");
}
return new KakaoUserInfo(id,email ,nickname, picture);
}
}
UserService.java
public HeaderDto kakaoLogin(String authorizedCode) {
// 카카오 OAuth2 를 통해 카카오 사용자 정보 조회
KakaoUserInfo userInfo = kakaoOAuth2.getUserInfo(authorizedCode);
// 랜덤이름 생성
String[] ranpre = {"살아가는","눈물의","센치한","고난의","무야호","해맑은","야구하는","내가바로","도전하는","인상적인"};
String[] ransuf = {"연어","고등어","파도","조각상","키보드","피카츄","프로도","두부","댕댕이","반죽" };
Random random = new Random();
Long kakaoId = userInfo.getId();
String nickname = ranpre[random.nextInt(10)+1]+ransuf[random.nextInt(10)+1];
String password = kakaoId + Pass_Salt;
//nullable = true
String picture = userInfo.getPicture();
String email = userInfo.getEmail();
User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);
if (kakaoUser != null) {
// 카카오 로그인 유저가 있다면 변경감지
userRepository.save(kakaoUser);
} else { // 새 유저
kakaoUser = User.builder()
.userid(email)
.username(nickname)
.picture(picture)
.kakaoId(kakaoId)
.password(password)
.build();
userRepository.save(kakaoUser);
}
Authentication kakaoUsernamePassword = new UsernamePasswordAuthenticationToken(email, password);
// 생성된 kakaoUser를 스프링 시큐리티에 적용하는 과정
UserDetails userDetails = new UserDetailsImpl(kakaoUser);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
HeaderDto headerDto = new HeaderDto();
// 로그인 처리 후 해당 유저 정보를 바탕으로 JWT토큰을 발급하고 해당 토큰을 Dto에 담아서 넘김
User member = userRepository.findByKakaoId(kakaoId).orElseThrow(()
-> new IllegalArgumentException("존재하지 않는 유저입니다."));
headerDto.setTOKEN(jwtTokenProvider.createToken(member.getUserid(), member.getId(), member.getUsername(), member.getPicture()));
return headerDto;
}
UserController.java
//카카오 로그인 api로 코드를 받아옴
@GetMapping("/user/kakao/callback")
@ApiOperation(value = "카카오유저 로그인", notes = "카카오유저 로그인")
@ResponseBody
public HeaderDto kakaoLogin(@RequestParam(value = "code", required = false) String code)
{
return userService.kakaoLogin(code);
}
'JWT' 카테고리의 다른 글
5. [SpringBoot JWT 튜토리얼] 회원가입, 권한검증 (0) | 2022.12.19 |
---|---|
4. [SpringBoot JWT 튜토리얼] DTO, Repository, 로그인 (0) | 2022.12.17 |
Spring Security 구조(아키텍처) (1) | 2022.12.17 |
3. [SpringBoot JWT 튜토리얼] JWT 코드, Security 설정 추가 (0) | 2022.12.16 |
Spring Security (0) | 2022.12.11 |