자바 뚝딱거리기

[JAVA] 네이버 소셜 로그인 REST API 적용하기

bimppap 2021. 7. 17. 21:11

 

Java SpringBoot 환경에서 네이버 소셜 로그인을 구현하기로 했다. OAuth는 사용하지 않았다.

이전에 카카오 소셜 로그인을 진행했기에 한결 수월하게 하지 않을까? 했지만 어쩌다보니 삽질을 많이 했다... 🥲

아무튼 나처럼 삽질하는 사람이 늘지 않기를 바라며 과정을 정리해보기로 했다. 순서는 다음과 같다.

 

1. Naver Developer 에서 클라이언트 ID, 클라이언트 시크릿을 발급받는다.

2. 키들을 이용해 인가 코드 발급받기

3. 인가 코드를 활용해 토큰 발급받기

4. 토큰으로 사용자 정보 가져오기

 


1.  Naver Developer 에서 키 발급받기

Naver Developers 에 접속해 Application - 애플리케이션 등록을 클릭한다.

애플리케이션 등록을 위해 애플리케이션 이름, 사용 API을 설정한다.

사용 API를 네아로(네이버 아이디로 로그인)로 설정하면 필수 또는 선택으로 제공 정보를 선택 가능한다.

환경 설정을 추가한다. PC 웹을 기준으로 설명하면 서비스 URL 과 Callback URL 을 입력해야 한다.

이후 등록하기 버튼을 누르면 애플리케이션이 등록되면서 개요를 확인할 수 있다. [API 설정] 카테고리에서 처음 입력한 정보를 수정하거나 로고 이미지를 등록할 수 있다. 여기서 클라이언트 ID와 시크릿을 알 수 있다. 시크릿은 필요할 때 재발급 받을 수 있다.

또한 카카오 소셜 로그인과 다르게 네이버는 처음 앱을 등록할 땐 [멤버관리] 카테고리에 포함된 아이디로만 네이버 소셜 로그인을 사용할 수 있게 한다. 모든 아이디가 네이버 소셜 로그인을 사용하려면(=서비스를 실제공하려면) 네아로 검수요청을 받아야 한다.

앱을 등록한 아이디는 기본적으로 관리자 ID에 포함된 상태다.

 

2. 키들을 이용해 인가 코드 발급받기

네이버 로그인 API 명세를 통해 자세한 요청, 응답 양식을 확인할 수 있다.

우리 팀의 경우 요청 API를
- 인가 코드 받기 : /naver/oauth
- 리다이렉트 URL : /naver/callback
로 하였다. 각자 프로젝트의 명세서에 따라 이 부분은 달라질 수 있음을 유의해두자.

인가 코드 받기

@Controller
@RequestMapping("/api/naver")
public class NaverController {

    @GetMapping("/oauth")
    public String naverConnect() {
        // state용 난수 생성
        SecureRandom random = new SecureRandom();
        String state = new BigInteger(130, random).toString(32);
        
        // redirect
        StringBuffer url = new StringBuffer();
        url.append(NAVER_AUTH_URI + "/oauth2.0/authorize?");
        url.append("client_id=" + 발급받은 클라이언트 키);
        url.append("&response_type=code");
        url.append("&redirect_uri=http://localhost:8080/api/naver/callback");
        url.append("&state=" + state);

        return "redirect:" + url;
    }

 

3. 인가 코드를 이용해 토큰 발급 받기

    @RequestMapping(value = "/callback", method = {RequestMethod.GET,
        RequestMethod.POST}, produces = "application/json")
    public void naverLogin(@RequestParam(value = "code") String code,
        @RequestParam(value = "state") String state) {
        // 네이버에 요청 보내기
        WebClient webclient = WebClient.builder()
            .baseUrl("https://nid.naver.com")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .build();

        JSONObject response = webclient.post()
            .uri(uriBuilder -> uriBuilder
                .path("/oauth2.0/token")
                .queryParam("client_id", 발급받은 클라이언트 ID)
                .queryParam("client_secret", 발급받은 클라이언트 시크릿)
                .queryParam("grant_type", "authorization_code")
                .queryParam("state", state)
                .queryParam("code", code)
                .build())
            .retrieve().bodyToMono(JSONObject.class).block();
            
        // 네이버에서 온 응답에서 토큰을 추출
        String token = response.get("access_token");
    }

 

4. 토큰으로 사용자 정보 가져오기

    @RequestMapping(value = "/callback", method = {RequestMethod.GET,
        RequestMethod.POST}, produces = "application/json")
    public void naverLogin(@RequestParam(value = "code") String code,
        @RequestParam(value = "state") String state) {
        // 생략...
        String token = response.get("access_token");
        getUserInfo(token);
    }

     public void getUserInfo(String accessToken) {
        // 사용자 정보 요청하기
        WebClient webclient = WebClient.builder()
            .baseUrl("https://openapi.naver.com")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .build();

        JSONObject response = webclient.get()
            .uri(uriBuilder -> uriBuilder
                .path("/v1/nid/me")
                .build())
            .header("Authorization", "Bearer " + accessToken)
            .retrieve()
            .bodyToMono(JSONObject.class).block();
        
        // 원하는 정보 추출하기
        Map<String, Object> res = (Map<String, Object>) response.get("response");
        String id = (String) res.get("id");
        String nickName = (String) res.get("nickname");
        String profileImage = (String) res.get("profile_image");
     }

 

5. 참고 자료

https://bumcrush.tistory.com/151

https://www.wrapuppro.com/programing/view/X4uLaFMDTh6tVaN

 

6. 사담

네이버는 카카오와 다르게 앱 내 로그아웃 기능을 지원하지 않는다. 자체적으로 로그아웃 api를 구현해야 한다. 🥲

또한, 소셜 로그인을 하나만 사용한다면 몰라도 여러개를 사용한다면 엑세스 토큰의 유효성을 확인할 때 어딜 통해 로그인했는지 확인해야 한다는 번거로움이 생긴다. 

이를 해결하기 위해 고민하다 생각해낸 방법은, 어디서 로그인을 하든 앱 내에서 토큰을 생성해 네이버 또는 카카오 인증 서버를 찌르지 않고 자체적으로 토큰의 유효성을 검사하도록 했다. JWT를 이용해 구현했는데, 자세한 내용은 글로 써 올릴 예정이다.