본문 바로가기

코딩/Java, SpringBoot

springboot OAuth 네이버 아이디로 로그인

@
처음에 네이버 로그인을 구현할 때 매우 힘들었던 기억이 있어서 남겨놓으려고 함.

기본적으로 아래에 있는 OAuth 순서를 잘 살펴보자. 본 그림 중에 젤 자세하다. 글을 읽다가 이해가 안되면 수시로 올라와서 보도록 하자.

@

먼저 혼자 하고싶고 OAuth에 대충 안다면 네이버 developers들어가서 API명세와 같이 다른거 보지말고 튜토리얼 - web애플리케이션 보면 된다. 설명이 잘되어있음.

https://developers.naver.com/docs/login/web/web.md

 

Web 애플리케이션 - LOGIN

네이버 로그인은 서버 사이드 언어인 PHP나 Java로 개발한 웹 애플리케이션에도 적용할 수 있습니다. 또한 프런트엔드에서 사용하는 JavaScript를 사용해도 적용할 수 있습니다. API 호출 예제 예제

developers.naver.com

 

 

참고로 위의 글을 읽다보면 stateToken, state라는 말이 나온다.

둘은 같은 말임. stateToken은 내가 임의로 지정하면 되는 것이다. 서버에서 사용할 수 있는 문자열을 아무렇게 나 정하면 된다.

예시)

private static final String stateToken = "aldfjaskdfjs";

 

@

먼저 네이버에 앱, 웹을 등록해야한다.

원하는 정보를 선택한다. 참고로 앱을 등록할 때는 다운로드 URL을 적으라고 하는데 일단 없는 주소를 넣어도 괜찮다고 들었다..확실하지는 않음... 찾아보길..

 

주의할 점이 있다. 클라이언트, 우리 서버, 네이버가 있다고 하자.

웹프로젝트의 경우 서버관리자가 OAuth를 신청하는게 맞지만 앱 프로젝트의 경우에는 서버의 관여없이 클라이언트 - 네이버의 통신으로 클라이언트가 사용자 정보를 모두 받아서 서버에게 전송하는 프로토콜이기 때문에 클라이언트가 이를 신청하고 관리하는게 편하다.  즉 서버는 결과로 넘어오는 사용자 정보만 얻으면 됨. stateToken, OAuth 신청, applicationId, secretKey 모두 클라이언트에서 알아서 한다.

 

참고로 네이버 로그인 검수를 받기 전까지는 개발용으로만 로그인이 허락된다. 앱을 등록한 사람과 추가로 개발용 네이버 아이디를 여러 개 등록할 수 있음.(팀원을 아이디를 등록하면 된다.) 등록된 아이디로만 로그인 가능. 검수를 받고 실제 서비스에 사용하더라도 사용자가 이름, 이메일 주소 등 요구하는 정보에 동의하지 않으면 아무것도 들어오지 않는다. 아래에도 써져있지만 기본적으로 사용자 식별자는 제공되기 때문에 무조건 들어오는 정보는 이것 밖에 없다.

하고자 하는 말 : table not null 설정해놓으면 안됨!

@
서비스 url은 개발 중일 때는 아래와 같이 localhost라고 적어주면 된다. AWS서버에 올린다면 서버 주소를 넣어주면 됨.

callback url은 사용자를 리다이렉트 시키는 url인데 이때 이 url로 파라미터를 담아서 네이버가 정보를 담아준다.

네이버가 바로 내 서버로 쏘는게 아니라 사용자를 리다이렉트 시키기 때문에 localhost라고 적으면 된다.!(네이버가 바로 정보를 서버로 쏴주는 것이라면 내 IP를 입력하고 포트포워딩도 되있는 상황에서만 실습이 가능할 것이다...)

@

성공적으로 추가하고나면 아래 창에서 id, secret정보를 확인하자.

@

연습용으로 아래와 같은 코드를 작성했다.

redirectURL은 위에서 설정한 http://localhost/login/naver이다.

stateToken은 내가 임의로 설정하면 된다. 자신의 이름을 해도 되고 아무거나 상관없다. URL에 들어가니까 /와 같은 녀석들은 사용하지 말자.! 나는 아래와 같은 stateToken을 사용했다... (원래 JWT토큰으로 사용하려다가...JWT안하게 되어서 그대로 사용함. ==이 있는데도 잘 동작하네??)

String stateToken = "UE5VLXpoZGhmaGQzMy1rLWhhY2thdGhvbg==";

return문에 있는 주소는 위에서 언급한 네이버 튜토리얼에서 제공해준다. 그것을 복붙하고 token, redirectURL만 자리에 넣어준 것이다!

참고로 redirectURL을 쿼리 스트링으로 보내야하기 때문에 URLencode해서 보내야한다.!

@GetMapping("/login")
public String loginNaver() {

    String encodedRedirectURL = null;

    try {
        encodedRedirectURL = URLEncoder.encode(redirectURL, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }
    return "https://nid.naver.com/oauth2.0/authorize?client_id=i6vA823oE3F_9QtAonj6&response_type=code&redirect_uri="
            + encodedRedirectURL + "&state=" + stateToken;
}

@

위와 같이 코드를  짜고 localhost/login에 접속해보면 아래와 같은 URL이 출력된다. 이 주소를 복사해서 접속해보자.

접속하면 아래와 같은 창이 자동으로 뜬다. 로그인 해보자.! 여기서 로그인을 하면 OAuth에서 사용하는 code와 앞에서 우리가 임의로 설정한 stateToken을 준다. 

 

@

지금까지 redirectURL을 사용하지 않았다. 이제 사용할 차례이다. 앞서 로그인을 하면 네이버는 사용자를 내가 네이버에 앱등록할 때 제출한 url로 리다이렉트 시킨다. (그래서 redirectURL임.) 우리는 localhost/login/naver를 사용했기 때문에 아래와 같은 코드를 작성하자. post가 아니라 get으로 오기 때문에 @GetMapping을 써줘야한다. 

@GetMapping("/login/naver")
public String loginNaver(HttpServletRequest httpServletRequest) {
    System.out.println(httpServletRequest.getQueryString());

위의 코드에서 print한 결과는 아래와 같다. code값이 넘어오고 state값은 앞에서 우리가 설정한 그대로이다. 여기서 state값이 우리의 state값과 같은지 검사하고 다음 로직을 수행할 수 있다. (검사는 네이버에서 자동으로 하는 것이 아니라 웹이라면 내 서버에서, 앱이라면 클라이언트에서 직접 해야한다.) 이게 state의 용도임. 그래서 아무렇게나 정의해도 된다는 것임.

위 코드의 실행 결과 : code=mI5RlKLWWqmbK0AAB7&state=UE5VLXpoZGhmaGQzMy1rLWhhY2thdGhvbg==

code, state의 값을 쿼리스트링으로 주고 있다.


@

위에서 일부분만 보여줬던 코드의 완성본이다. restTemplate 사용법은 구글링해보자!

위에서 봤던 redirectURL이다. 위에서는 실습차원에서 getQueryString()만 출력해봤지만 OAuth를 위해서는 해당 정보를 사용해서 다시 요청을 보내야한다. 그럼 OAuth의 목표인 accessToken을 받을 수 있다.

@GetMapping("/login/naver")
public String loginNaver(HttpServletRequest httpServletRequest) {
    System.out.println(httpServletRequest.getQueryString());

    String url = "https://nid.naver.com/oauth2.0/token?client_id="
            + clientID
            + "&client_secret="
            + secret
            + "&grant_type=authorization_code&state=" +
            stateToken +
            "&code=" +
            httpServletRequest.getParameter("code");

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
    System.out.println("body! : "+response.getBody());//아래 출력결과 스크린샷이 있다.
    System.out.println("header! : "+response.getHeaders().toString());//아래 출력결과 스크린샷이 있다.

    return "success!";
}

사용자를 url로 안내하고 로그인을 성공하면 redirectURL로 위와 같은 정보를 받아올 수 있다. get방식으로 가져오기 때문에 @GetMapping을 사용해야한다. body에 accessTokenrefreshToken이 있는 것을 알 수 있다. 

accessToken과 RefreshToken을 어떻게 사용하는지는  위에서 언급한 네이버 디벨롭스의 튜토리얼을 참고하자.!

 

@
redirect_url은 get으로 받아야한다. post로받으면 아래사진의 맨 밑줄과 같은 예외 뜬다. GET not supported!

@

참고

앞서 아래의 url을 복사해서 접근했을 때 네이버 로그인 창이 나왔다.

서버를 재시작한다음에 새로고침을 하면 error뜬다. 주소창에 다시 아래의 주소를 복붙해야 올바르게 동작한다.

@
아래는 전체코드. (OAuth를 연습용이다.) 

 @GetMapping("/login/naver")
    public String loginNaver(HttpServletRequest httpServletRequest) {
        System.out.println(httpServletRequest.getQueryString());

        String url = "https://nid.naver.com/oauth2.0/token?client_id="
                + clientID
                + "&client_secret="
                + secret
                + "&grant_type=authorization_code&state=" +
                stateToken +
                "&code=" +
                httpServletRequest.getParameter("code");

//        HttpHeaders headers = new HttpHeaders();
//        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
//        HttpEntity<?> httpEntity = new HttpEntity<>(body, headers);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
        System.out.println("body! : "+response.getBody());//accessToken, refreshToken 가져옴
        System.out.println("header! : "+response.getHeaders().toString());
        Map<String, String> tokens;
        try {
            tokens = Mapper.objectMapper.readValue(response.getBody(), Map.class);

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        System.out.println("tokens : "+tokens);
        tokens.get("refresh_token");

        HttpHeaders headers1 = new HttpHeaders();
        headers1.setBearerAuth(tokens.get("access_token"));
        System.out.println("headers1! : "+headers1);
        HttpEntity entity = new HttpEntity(headers1);
        url = "https://openapi.naver.com/v1/nid/me";

        ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        System.out.println(exchange);


        return "success!";
    }

@
특이사항

네이버의 경우 안드로이드 앱에서 oauth를 사용하려면 아래와 같은 정보가 필요하다. 개발단계에서 이를 어떻게 입력하고 진행하겠는가... 

그래서 웹으로 등록하고 앱에서 사용하는 방법을 알아보자.! 서버가 있으면 다 웹으로 등록해서 진행 가능한 것 같다. 웹, 안드로이드에 따라 제공하는 기능이 달라지거나 하지는 않는 듯. 결과적으로 나는 안드로이드 앱의 서버를 맏았는데 "모바일 웹"으로 등록해서 잘 동작했다. 아래와 같이 등록했다. 다만 이렇게 하면 앱에서 naver, chrome과 같은 앱을 사용해서 로그인하기 때문에 사용자가 우리앱->크롬에서 로그인 -> 다시 우리앱. 의 흐름을 따르게 된다. 크롬에 있는 동안에는 자동으로 리다이렉트 되기 때문에 자유도가 떨어진다.

위 사진에서 말하고 싶은 것 : AWS에서 동작할 때도 서비스 url은 localhost로 입력해도 동작했다라는 점이다.

callback url은 OAuth을 배울 때 나와있었다. 그런데 서비스 URL은 내가 배웠던 OAuth에서 나와있지 않았다. 즉 이 url이 어디서 어떤 용도로 사용되는지 알지 못했다. 그래서 실험삼아 localhost로 넣고 테스트를 진행했는데 성공했다. 

AWS에 앱을 올려놓은 상황이었다. 정상적이라면 AWS서버의 url을 넣어야하지만 localhost에 넣어도 잘 동작함을 알 수 있었다. 아마도 나중에 검수하거나 할 때 사용하는 것 아닐까 싶다!