TOP
본문 바로가기
[Java]/Spring Boot

[Java] Spring Boot: 네이버 로그인 구현하기

by 기록자_Recordian 2025. 4. 21.
728x90
반응형
이전 내용
 

[Java] Spring Boot: 카카오 로그인 기능 추가하기

이전 내용 [Java] Spring Boot: 구글 로그인 기능 추가하기이전 내용 전반적인 순서1. Gradle 또는 Maven 의존성 추가2. 구글 API Console 설정 - 구글 개발자 콘솔 프로젝트 생성 → OAuth 2.0 클라이언트 ID 생성

puppy-foot-it.tistory.com


네이버 로그인 구현하기

 

이전에 구글과 카카오 로그인 기능을 구현했으므로, 이번에는 네이버 로그인 기능을 구현해 본다.

네이버 로그인 관련 정보는 네이버 디벨로퍼에서 상세한 정보를 얻을 수 있다.

 

먼저 Naver Developers에서 Application - Application 등록 클릭

애플리케이션 이름을 넣고, 사용 API를 체크하고 (본인 환경에 맞게)

 

서비스 환경은 필자의 경우 웹사이트이므로 PC웹을 선택하고, 서비스 URL과 네이버로그인 콜백URL(Redirect URI)를 입력하고, 밑에 동의항목을 체크한 후 [등록하기] 클릭

 

이 과정을 거치면 네이버의 Client ID와 Client Secret 이 생성된다. 보안을 유지하며 잘 보관한다.


application.properties 작성하기

 

그리고 IDE(필자의 경우 인텔리제이)로 돌아와서 application.properties에 해당 내용을 추가한다.

spring.security.oauth2.client.registration.naver.client-id= [네이버 클라이언트 ID]
spring.security.oauth2.client.registration.naver.client-secret= [네이버 클라이언트 SecretKey]
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.redirect-uri=https://localhost:9091/login/oauth2/code/naver
spring.security.oauth2.client.registration.naver.scope=[사용 API 범위]
spring.security.oauth2.client.registration.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.authorization_uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token_uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user_name_attribute=response

 


네이버 로그인 버튼 이미지
다운 및 적용하기

 

먼저, 하단의 사이트로 이동해서 버튼 이미지를 다운 받는다.

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

압축을 풀고, 사용하고자 하는 이미지를 하나 복사한 후

 

main/resources/static/img 폴더 내로 이동시킨 뒤 파일명 변경

 

그리고 oauthLogin.html 파일에 네이버 로그인 버튼을 삽입시켜준다.

<div class = "mb-2">
  <a href="/oauth2/authorization/google">
    <img src="/img/google.png">
  </a>
</div>
<div class = "mb-2">
  <a href="/oauth2/authorization/kakao">
    <img src="/img/kakao.png">
  </a>
</div>
<div class = "mb-2">
  <a href="/oauth2/authorization/naver">
    <img src="/img/naver.png">
  </a>
</div>

 

그리고나서  OAuth2SuccessHandler 파일의 코드도 수정해주고

package com.example.spro01.config.oauth;

import com.example.spro01.config.jwt.TokenProvider;
import com.example.spro01.domain.RefreshToken;
import com.example.spro01.domain.User;
import com.example.spro01.repository.RefreshTokenRepository;
import com.example.spro01.service.UserService;
import com.example.spro01.util.CookieUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.time.Duration;
import java.util.Map;

@RequiredArgsConstructor
@Component
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    public static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken";
    public static final long REFRESH_TOKEN_DURATION = Duration.ofDays(14).toMillis();
    public static final long ACCESS_TOKEN_DURATION = Duration.ofDays(1).toMillis();
    public static final String REDIRECT_PATH = "/articles";

    private final TokenProvider tokenProvider;
    private final RefreshTokenRepository refreshTokenRepository;
    private final OAuth2AuthorizationRequestBasedOnCookieRepository authorizationRequestRepository;
    private final UserService userService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();

        // 사용자 이메일 추출
        String email = null;

        // 카카오 계정에서 이메일 추출
        if (oAuth2User.getAttributes().containsKey("kakao_account")) {
            Map<String, Object> kakaoAccount = (Map<String, Object>) oAuth2User.getAttributes().get("kakao_account");
            if (kakaoAccount != null) {
                email = (String) kakaoAccount.get("email");
            }
        }

        // 구글 계정에서 이메일 추출
        else if (oAuth2User.getAttributes().containsKey("email")) {
            email = (String) oAuth2User.getAttributes().get("email");
        }

        // 네이버 계정에서 이메일 추출
        else if (oAuth2User.getAttributes().containsKey("response")) {
            Map<String, Object> responseAttributes = (Map<String, Object>) oAuth2User.getAttributes().get("response");
            email = (String) responseAttributes.get("email");
        }

        // 이메일이 null인 경우 적절한 예외 처리를 추가할 수 있음
        if (email == null) {
            // 이메일을 찾을 수 없을 때 처리 로직 추가 (예: 예외 발생)
            throw new IllegalArgumentException("Email not found in OAuth2 user attributes");
        }

        User user = userService.findByEmail(email);

        // 리프레시 토큰 생성 -> 저장 -> 쿠키에 저장
        String refreshToken = tokenProvider.generateToken(REFRESH_TOKEN_DURATION, user);
        saveRefreshToken(user.getId(), refreshToken);
        addRefreshTokenToCookie(request, response, refreshToken);

        // 액세스 토큰 생성 -> 패스에 액세스 토큰 추가
        String accessToken = tokenProvider.generateToken(ACCESS_TOKEN_DURATION, user);
        String targetUrl = getTargetUrl(accessToken);

        // 인증 관련 설정값, 쿠키 제거
        clearAuthenticationAttributes(request, response);

        // 리다이렉트
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }

    // 생성된 리프레시 토큰을 전달받아 데이터베이스에 저장
    private void saveRefreshToken(Long userId, String newRefreshToken) {
        RefreshToken refreshToken = refreshTokenRepository.findByUserId(userId)
                .map(entity -> entity.update(newRefreshToken))
                .orElse(new RefreshToken(userId, newRefreshToken));

        refreshTokenRepository.save(refreshToken);
    }

    // 생성된 리프레시 토큰을 쿠키에 저장
    private void addRefreshTokenToCookie(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
        int cookieMaxAge = (int) REFRESH_TOKEN_DURATION;
        CookieUtil.deleteCookie(request, response, REFRESH_TOKEN_COOKIE_NAME);
        CookieUtil.addCookie(response, REFRESH_TOKEN_COOKIE_NAME, refreshToken, cookieMaxAge);
    }

    // 인증 관련 설정값, 쿠키 제거
    private void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
        super.clearAuthenticationAttributes(request);
        authorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
    }

    // 액세스 토큰을 패스에 추가
    private String getTargetUrl(String token) {
        return UriComponentsBuilder.fromUriString(REDIRECT_PATH)
                .queryParam("token", token)
                .build()
                .toUriString();
    }
}

 

서버를 다시 시작해보면

 

네이버 로그인이 정상적으로 작동하는 것을 확인할 수 있다.


[참고]

https://notspoon.tistory.com/41

https://developers.naver.com/main/

NAVER DEVELOPERS

https://velog.io/@leeseunghee00/Spring-Security%EC%99%80-OAuth-2.0%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-lnk49ddi

 


다음 내용

 

 

728x90
반응형