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에 대한 정보가 많이 부족하기에 혹시라도 지나가시던 고수님께서 보시고 틀린점이나 추가할 점을 발견하신다면 한소리 부탁드립니다..
참고:
'Spring' 카테고리의 다른 글
[Spring] JWT를 사용한 로그인 구현 1 (2) | 2021.08.02 |
---|---|
[Spring] H2 데이터베이스 사용하기 (0) | 2021.08.01 |
[Spring] 카카오 아이디로 로그인하기 - 1 (0) | 2021.07.28 |
[Spring] Spring이란 (0) | 2021.06.24 |
[Spring] 네이버 아이디로 로그인(네아로) API 사용 - 1 (1) | 2021.06.21 |