OAuth2
이전 시간을 통해 카카오 로그인의 흐름과 그 코드를 알아봤다. 그러면 이 이해를 바탕으로 횡이동 하면 된다. 어떻게? 비교하면서.
어차피 OAuth2는 표준 프로토콜이다. 구현 방식은 크게 다르지 않을 것이다. 비교하면서 가자. 이번엔 코드스니펫이 아닌 직접 작성하는 코드기 때문에 완성본 예시는 없다. 그럼지금바로당장출발렛츠고
네이버 공식문서 링크는 여기 있다.
1. 인가 코드로 액세스 요청
전체적인 코드의 흐름은 비슷할 것으로 생각된다. 대신 파라미터들은 다를테니, 이번엔 네이버 공식문서 친구와 함께 해보겠다.
먼저 필요한 것은 body에 필요한 정보들이다. 찾아오자.
필요한 정보는 이 파트에 있다.
여기는 어디로 요청해야 하는지 최상단에 명시해주네.. 이전 카카오 문서에서 아쉬웠던 점이 잘 충족이 된 것 같다. 그러면 이걸 보고 이제 코드를 써보자. 참고로 나는 기존 코드스니펫 방식을 따라 POST 방식으로 요청했지만, GET방식도 가능하다.
카카오와 다른 점은, client_secret이 선택사항이었던 카카오와는 달리 네이버는 반드시 넣어줘야 한다는 점이다. 이 점 유의하자.
아.. 근데 반드시 필요한 정보 중 state에 대한 부분. 이게 뭘까. 나는 지금 이 정보는 제공받지 못 했는데. 흠.....
공식문서를 조금 더 찾아보자.
찾아보니, 애초에 프론트엔드에서 요청을 할 때부터 state 값을 파라미터로 작성하게 되어있었다. 맥락을 보니 임의의 값을 넣으면 되는 것 같다. 일단 아무 값이나 넣어서 인증을 시도해봐야겠다.
프론트엔드 단에서 인증을 시도할 때 사용한 URL은 다음과 같다.
https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id={YOUR_CLIENT_ID}&state=randomtext&redirect_uri={YOUR_CALLBACK_URL}
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HTTP Body 생성
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", clientId);
body.add("client_secret", secretKey);
body.add("code", code);
body.add("state", state);
// HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> naverTokenRequest =
new HttpEntity<>(body, headers);
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
"https://nid.naver.com/oauth2.0/token",
HttpMethod.POST,
naverTokenRequest,
String.class
);
그렇게 받아온 state값까지 포함해서 파라미터에 넣고 요청까지 보내줬다. 그러면 이제 응답 받아올 시간.
응답 정보 역시 친절하게 잘 나와있다.
코드 나와라 뿅.
// HTTP 응답 (JSON) -> 액세스 토큰 파싱
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
return jsonNode.get("access_token").asText();
이렇게 해서 만들어진 메소드 완성본은 다음과 같다.
private String getAccessToken(String code, String state) throws JsonProcessingException {
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HTTP Body 생성
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", clientId);
body.add("client_secret", secretKey);
body.add("code", code);
body.add("state", state);
// HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> naverTokenRequest =
new HttpEntity<>(body, headers);
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
"https://nid.naver.com/oauth2.0/token",
HttpMethod.POST,
naverTokenRequest,
String.class
);
// HTTP 응답 (JSON) -> 액세스 토큰 파싱
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
return jsonNode.get("access_token").asText();
}
START: execution(ResponseEntity com.teamproj.backend.service.NaverUserService.naverLogin(String,String))
AAAAOjsnCUOZnyn6yRdazArijMkFkQ5PLT_OrdzYRBoxCqQMN3iLH-jUwE9jlf1QJKT-hXeaBmt8tb0yJkWw8uH4TdI
END: execution(ResponseEntity com.teamproj.backend.service.NaverUserService.naverLogin(String,String)) 340ms
sout 찍어보니 코드가 잘 나오는 모습. 옼ㅋㅋㅋㅋㅋ 나는 틀리지 않았다.
근데 여기서 느낀 점은, state 값이 변하지 않으면 이 코드도 언제나 동일하게 나온다는 점이다. 이런 점을 고려해서 state는 난수를 사용하는 것이 좋을 것 같다는 생각이 들었다.
2. 액세스 토큰으로 사용자 정보 가져오기
이 부분은 생각해보면 양식은 크게 다르지 않을 것 같다. 왜냐면 OAuth2는 표준이니까. 그래도 한 번 문서를 확인해본다.
이봐 OAuth2 ww 오마에 이렇게 응용하기 편해도 되는거냐구?(草)
주소도 이렇게 잘 알려줬겠다. 이걸 그대로 코드에 붙이기만 하면 될 것이다.
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> naverUserInfoRequest = new HttpEntity<>(headers);
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
"https://openapi.naver.com/v1/nid/me",
HttpMethod.POST,
naverUserInfoRequest,
String.class
);
이번엔 필요한 정보값을 가져와야 한다. 나에게 필요한 정보는 현재
1. 사용자의 고유 정보를 인식하기 위한 id값
2. 사용자의 닉네임
3. 사용자의 프로필사진
세 가지이다. id값을 가져오는 이유는 DB에 정보를 저장할 때 회원 정보가 이미 존재하는지 알아내고, 존재한다면 로그인처리, 없다면 회원가입 처리를 하기 위해서이다. 그러면 이젠 정보를 어떻게 가져오는지 또 살펴보자.
이렇게 보니 양식이 어떻게 되는 건지 좀 헷갈린다. "response/id"로 가져와야하나? 아니면 그냥 "id"로 가져와야하나? json 형식으로 보여줬다면 조금 더 직관적인 느낌이었을지도 모르겠다. 대강 감은 오지만 일단 확실한 정보를 얻기 위해 sout을 찍어봤다.
{
"resultcode": "00",
"message": "success",
"response":
{
"id": "GrzApdKssmguX3FcB8BG8QDUh9tKx45bXhEnOBxq_IU",
"nickname": "자비",
"profile_image": "https://ssl.pstatic.net/static/pwe/address/img_profile.png"
}
}
오케이. 이런식으로 가져오는 거였군. "바로 적용"
private NaverUserInfoDto getNaverUserInfo(String accessToken) throws JsonProcessingException {
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> naverUserInfoRequest = new HttpEntity<>(headers);
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
"https://openapi.naver.com/v1/nid/me",
HttpMethod.POST,
naverUserInfoRequest,
String.class
);
// HTTP 응답 받아오기
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
Long id = jsonNode.get("response").get("id").asLong();
String nickname = jsonNode.get("response").get("nickname").asText();
String profileImage = jsonNode.get("response").get("profile_image").asText();
return NaverUserInfoDto.builder()
.id(id)
.nickname(nickname)
.profileImage(profileImage)
.build();
}
정보는 받아왔다. sout 찍어보자.
id : 0
nickname : 자비
profileImage : https://ssl.pstatic.net/static/pwe/address/img_profile.png
잘 됨 ㅋㅋ
이제 나머지는 백엔드에서 알아서 잘 해주면 되는 영역이다. 와하하~
이로써 나는 OAuth2에 대해 알 수 있었고, 실제로 구현도 해봤다. 혹시 WebSecurity를 이용하지 않고 회원가입 절차를 진행하고 싶은 사람들은 이렇게 해보면 좋을 것 같다. 아이좋아.
공식문서는 언제나 사람을 강하게 만든다. 이 문서를 해석해낸 순간 모든 것을 해낼 수 있을 것만 같은 기분이 들게 한다. 이게... 더닝크루거 효과?
'기술 > Spring-Boot' 카테고리의 다른 글
[Ubuntu/nginx] 스프링 부트 및 우분투 환경에서 nginx 이용한 무중단배포 구현 (0) | 2022.01.05 |
---|---|
[Ubuntu/Travis-CI/CodeDeploy] SpringBoot 환경 배포 자동화 환경 구축 (0) | 2022.01.04 |
[SpringBoot/OAuth2] WebSecurity 없이 REST API 환경에서 OAuth2 인증 구현하기 - 1. 카카오 로그인 (0) | 2022.01.02 |
[SpringBoot/Redis] SpringBoot에서의 Redis 기본 명령어 (0) | 2021.12.29 |
빌더패턴 제네릭 클래스에 적용하기 (1) | 2021.12.24 |
댓글