[Java] Naver Oauth2.0 로그인 구현

dustle·2023년 2월 28일
11

OAuth 2.0 로그인 구현 방식

프론트에서 인가코드를 요청하고 나머지를 백엔드에서 구현하는 방식으로 구현했습니다.

1. 네이버 디벨로퍼에 들어가 프로젝트를 등록합니다.

애플리케이션 이름에 프로젝트 명을 적습니다.
사용 API에 네이버 로그인을 고르고 필요한 최소 권한을 선택합니다.
로그인 오픈API 서비스 환경에 서비스 URL과 Callback Url을 쓰게 되어있는데, 서버 IP를 모자이크 된 부분에 넣습니다.

CallBack Url은 인가코드를 요청 한 후 redirect 되는 주소인데, 이 프로젝트에서 인가코드를 받는 기능을 프론트에서 구현하기 때문에 추후 변경 예정입니다.

2. 네이버 로그인 URL 만들어서 인가 코드 받기

요청 URL 뒤에 요청 변수 정보를 입력하면 지정해놓은 callback URL로 인가 코드가 나옵니다.

요청 URL은
https://nid.naver.com/oauth2.0/authorize?client_id={클라이언트아이디}&response_type=code&redirect_uri={개발자센터에등록한콜백URL}&state={상태토큰}
형식입니다.

(client_id는 여기 있습니다...)

state를 만드는 코드는 네이버 로그인 튜토리얼에 써있습니다.

// CSRF 방지를 위한 상태 토큰 생성 코드
// 상태 토큰은 추후 검증을 위해 세션에 저장되어야 한다.


public String generateState()
{
    SecureRandom random = new SecureRandom();
    return new BigInteger(130, random).toString(32);
}


// 상태 토큰으로 사용할 랜덤 문자열 생성
String state = generateState();
// 세션 또는 별도의 저장 공간에 상태 토큰을 저장
request.session().attribute("state", state);
return state;

하지만 저는 백엔드이기 때문에,,(내 일 아니라서) 어디서 주워왔습니다.

이렇게 만들어진 URL이
https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=(GOGOClientId)&state=hLiDdL2uhPtsftcU&redirect_uri=http://localhost:8080/naver/callback
가 됩니다.

위 주소로 들어가보면

이런 빈 창이 뜨게 되는데, code 뒤에 적힌 문자들이 인가 코드가 됩니다.

3. 인가코드와 필요 파라미터 받아서 토큰 발급하기

Post로 필요 파라미터들을 프론트에서 받아옵니다.
파라미터를 발급할 때 필요한 요청 변수는 아래와 같으므로 필요한 변수를 DTO로 만들어서 받았습니다.

OauthController.java

    @PostMapping("/naver")
    public ResponseEntity<TokenResponse> naverLogin(@RequestBody NaverLoginRequest naverLoginRequest) {
        OauthInfo naverInfo = naverOauthService.getNaverInfo(naverLoginRequest);

        TokenResponse tokenResponse = oauthMemberService.getAccessTokenWithOauthInfo(naverInfo);

        return ResponseEntity.ok(tokenResponse);
    }

NaverLoginRequest.java

@Getter
@NoArgsConstructor
public class NaverLoginRequest implements OauthLoginRequest {

    private String grantType;

    private String clientId;

    private String authorizationCode;

    private String state;
}

토큰을 요청하는 URL의 형식은
https://nid.naver.com/oauth2.0/token?client_id={클라이언트 아이디}&client_secret={클라이언트 시크릿}&grant_type=authorization_code&state={상태 토큰}&code={인증 코드}
이므로, DTO에서 받은 변수들을 조합합니다.

auth url과 Client Secret(client id 있는 곳에 있음)은 yml파일에서 가져와서 쓰게 하였습니다.

NaverApiClient.java

@Component
@RequiredArgsConstructor
public class NaverApiClient implements OauthApiClient {

    @Value("${oauth.naver.url.auth}")
    private String authUrl; //https://nid.naver.com

    @Value("${oauth.naver.url.api}")
    private String apiUrl;

    @Value("${oauth.naver.secret}")
    private String clientSecret;

    private final RestTemplate restTemplate;

    
    @Override
    public String getOauthAccessToken(String grantType, String clientId, String code, String state) {
        String url = authUrl + "/oauth2.0/token";

        HttpHeaders httpHeaders = newHttpHeaders();
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", grantType);
        body.add("client_id", clientId);
        body.add("client_secret", clientSecret);
        body.add("code", code);
        body.add("state", state);
        HttpEntity<?> request = new HttpEntity<>(body, httpHeaders);

        ResponseEntity<NaverToken> response = restTemplate.postForEntity(url, request, NaverToken.class);

        return Objects.requireNonNull(response.getBody()).getAccessToken();
    }

4. 토큰을 사용하여 사용자 정보 얻기


네이버는 다른 OAuth와 다르게 프로필 요청 URL에 별도 파라미터가 필요하지 않습니다.

토큰을 accessToken 변수에 저장 후

NaverOauthService.java

@Service
@RequiredArgsConstructor
public class NaverOauthService {
    private final OauthApiClient naverApiClient;

    public OauthInfo getNaverInfo(NaverLoginRequest naverLoginRequest) {
        String accessToken = naverApiClient.getOauthAccessToken(naverLoginRequest);
        OauthProfileResponse oauthProfile = naverApiClient.getOauthProfile(accessToken);

        return OauthInfo.builder()
                .email(oauthProfile.getEmail())
                .nickname(oauthProfile.getNickName())
                .type(Member.Type.NAVER)
                .authority(Member.Authority.ROLE_MEMBER)
                .build();
    }
}

헤더를 넣어서 반환해줍니다.

NaverApiClient.java

//apiUrl: https://openapi.naver.com
    @Override
    public OauthProfileResponse getOauthProfile(String accessToken) {
        String url = apiUrl + "/v1/nid/me";

        HttpHeaders httpHeaders = newHttpHeaders();
        httpHeaders.set("Authorization", "Bearer " + accessToken);

        HttpEntity<?> request = new HttpEntity<>(httpHeaders);
        ResponseEntity<NaverMyInfo> response = restTemplate.postForEntity(url, request, NaverMyInfo.class);

        return response.getBody();

요청을 하게 되면, JSON 형식으로 된 결과값을 받게 됩니다.

reponse 안에 있는 정보들이 필요하므로,

NaverMyInfo.java

@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class NaverMyInfo implements OauthProfileResponse {

    @JsonProperty("response")
    private Response response;

    @Getter
    @JsonIgnoreProperties(ignoreUnknown = true)//비어있는 Json 값들을 무시해 줌
    static class Response {
        private String email;
        private String nickname;
    }

    @Override
    public String getEmail() {
        return response.getEmail();
    }

    @Override
    public String getNickName() {
        return response.getNickname();
    }
}

클래스에 담아서 반환해줍니다. (NaverOauthService 참조)

이렇게 정보들을 가져온 후 내부에서 정보를 가공합니다.

전체 코드
https://github.com/GoGo-tm/GoGo_Server/pull/81

참조
네이버 디벨로퍼 로그인
네이버 디벨로퍼 튜토리얼

2개의 댓글

comment-user-thumbnail
2023년 2월 28일

그림이 있어서 더 이해하기 쉬웠어요~

1개의 답글