YS's develop story

Spring, OAuth2 + JWT 를 활용하여 소셜 로그인 구현하기 2편 (구글 및 네이버) [Spring 3.1.5, java 17] 본문

Spring

Spring, OAuth2 + JWT 를 활용하여 소셜 로그인 구현하기 2편 (구글 및 네이버) [Spring 3.1.5, java 17]

Yusang 2023. 12. 11. 22:01

 

 

이 글은 전글과 이어집니다.!

https://yusang.tistory.com/140

 

Spring, OAuth2 + JWT 를 활용하여 구글 소셜 로그인 구현하기 [Spring 3.1.5, java 17]

http://console.cloud.google.com/project Google 클라우드 플랫폼 로그인 Google 클라우드 플랫폼으로 이동 accounts.google.com 프로젝트 만들기 -> API 및 서비스 -> 사용자 인증정보 -> 사용자 인증 정보 만들기 -> OAu

yusang.tistory.com

 

과정 설명에 앞서서 전체적인 동작방식을 ppt로 만들어 보았습니다.

전체적인 동작 방식을 그림으로 정리하자면 아래와 같습니다.

 

 

 

네이버 개발자 사이트에 접속합니다.

그 후 아래와 같이 애플리케이션 등록을 합니다.

이때 네이버로그인 시 제공받을 정보를 선택합니다.

참고로 저는 ssl인증서를 받아서 등록했기에 https로 요청을 했습니다

 

등록 후 발급받은 id와 key를 기억해야 합니다.

 

 

아래의 네이버 로그인 개발가이드 공식문서를 참고하여 yml 파일에 코드를 추가합시다.

 

네이버 로그인 개발가이드 - LOGIN

네이버 로그인 개발가이드 1. 개요 4,200만 네이버 회원을 여러분의 사용자로! 네이버 회원이라면, 여러분의 사이트를 간편하게 이용할 수 있습니다. 전 국민 모두가 가지고 있는 네이버 아이디

developers.naver.com

 

 

 

 

 

네이버 소셜 로그인은 OAuth2.0 공식 지원 대상이 아니기에 provider가 기본적으로 제공되고 있지 않습니다.

그렇기에 provider 설정이 필요하고, google로그인과 달리 직접 지정해야 하는 값들이 있습니다.

 

전편에서 추가했었던 google 아래에 naver를 아래와 같이 추가해 줍니다.

이때 들여 쓰기에 유의해야 합니다.

IDE에서 인식해야 합니다.

security:
  oauth2:
    client:
      registration:
        google:
          client-id: ${GOOGLE_ID}
          client-secret: ${GOOGLE_KEY}
          scope:
            - email
            - profile

        naver:
          client-id: key
          client-secret: value
          scope:
            - name
            - email
          client-name: Naver
          authorization-grant-type: authorization_code
          redirect-uri: https://localhost:8080/login/oauth2/code/naver


      provider:
        naver:
          authorization-uri: https://nid.naver.com/oauth2.0/authorize
          token-uri: https://nid.naver.com/oauth2.0/token
          user-info-uri: https://openapi.naver.com/v1/nid/me
          user-name-attribute: response

 

 

구글 로그인때와 마찬가지로 아래에 엔드포인트로 보내면

네이버 로그인이 나오게 됩니다.

http://localhost:8080/oauth2/authorization/naver

 

그 후 System.out으로 정보를 출력하면 아래와 같이 로그인정보가 출력됩니다.

getAttributes: {resultcode=00, 
message=success, 
response={id=nsTFmLViERwRxCE_M03ep0uk6qXv148cUp9ppSQWpsk, 
email=개인정보, 
mobile=개인정보, 
mobile_e164=+개인정보,
name=이유상}}

 

 

이때 로그인 페이지에서 로그인을 수행하고,

승인하면 Spring Security가 제공한 콜백 엔드포인트로 다시 리다이렉트 됩니다.

여기서는 아까 설정 한  http://localhost:8080/login/oauth2/code/naver로 리다이렉트 됩니다.

 

배포된 환경에서 OAuth 로그인을 하려고 한다면

배포된 도메인도 같이 네이버 로그인 애플리케이션을 등록할 때 같이 입력해야지

배포환경에서도 OAuth 로그인을 할 수 있습니다.

 

승인된 리디렉션 URI를 등록하지 않는다면 

400 오류 redirect_uri_mismatch 에러가 발생하게 됩니다.

 

 

저번 포스팅에 google 소셜 로그인을 추가한 상황에서 naver 소셜 로그인을 추가하고자 했기 때문에

구글 로그인인지, naver로그인인지에 따라 상황을 분기하여야 합니다.

따라서 아래처럼 코드를 수정해 줍니다.

 

우리는 userRequest.getClientRegistration().getRegistrationId()를 통해서

google 로그인인지, naver 로그인인지 알 수 있습니다.

또한 OAuth2UserInfo interface을 생성하여

OAuth2.0 제공자들마다 응답해 주는 공통의 속성을 만들어 줍니다.

 

그리고 그 후 이를 implement 한 NaverUserInfo, GoogleUserInfo을 각각 생성해 줍니다.

그렇게 되면 아래와 같이 상황에 따라 맞는 정보를 생성하고 처리를 할 수 있습니다.

@Service
@RequiredArgsConstructor
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        //System.out.println("getClientRegistration: " + userRequest.getClientRegistration());
        //System.out.println("getAccessToken: " + userRequest.getAccessToken());
        //System.out.println("getAttributes: " + super.loadUser(userRequest).getAttributes());

        OAuth2User oauth2User = super.loadUser(userRequest);

        OAuth2UserInfo oauth2Userinfo = null;
        String provider = userRequest.getClientRegistration().getRegistrationId(); //google kakao facebook...

        if(provider.equals("google")){
            oauth2Userinfo = new GoogleUserInfo(oauth2User.getAttributes());
        } else if(provider.equals("naver")){
            oauth2Userinfo = new NaverUserInfo((Map)oauth2User.getAttributes().get("response"));
        }

        Optional<User> user = userRepository.findByEmailAndProvider(
            oauth2Userinfo.getEmail(),oauth2Userinfo.getProvider());

        //이미 소셜로그인을 한적이 있는지 없는지
        if (user.isEmpty()) {
            User newUser = User.builder()
                .email(oauth2Userinfo.getEmail())
                .username(oauth2Userinfo.getName())
                .password("OAuth2")  //Oauth2로 로그인을 해서 패스워드는 의미없음.
                .phonenumber(oauth2Userinfo.getPhoneNumber())
                .authority("ROLE_USER")
                .provider(provider)
                .build();

            userRepository.save(newUser);
            return new PrincipalDetails(newUser, oauth2User.getAttributes());
        } else {
            return new PrincipalDetails(user.get(), oauth2User.getAttributes());
        }
    }
}
public interface OAuth2UserInfo {
    String getProviderId();
    String getProvider();
    String getEmail();
    String getName();
    String getPhoneNumber();
}
public class NaverUserInfo implements OAuth2UserInfo {

    private Map<String, Object> attributes;

    public NaverUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get("id");
    }

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }

    @Override
    public String getPhoneNumber() {
        return (String) attributes.get("mobile");
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getProvider() {
        return "naver";
    }
}
public class GoogleUserInfo implements OAuth2UserInfo{

    private Map<String, Object> attributes;

    public GoogleUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get("sub");
    }

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }

    @Override
    public String getPhoneNumber() {
        return null;
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getProvider() {
        return "google";
    }
}

 

 

 

이제 아래 엔드포인트로 네이버 로그인을 요청해 봅시다.

localhost:8080/oauth2/authorization/naver

 

 

로그인 성공 시 발급받은 JWT를 확인할 수 있습니다.

JWT을 통한 사용자 인증도 잘되네요.

 

 

네이버 로그인을 서비스에서 제대로 사용하고자 한다면

네이버 검수요청을 진행해서 받아야 합니다.

그전까지는 멤버관리에서 등록한 아이디만 등록할 수 있습니다.

 

 

그래서 현재 네이버 로그인 과정을 정확히 캡처하여 검수 요청을 했습니다.!

 

 

 

또한 주의해야 할 점이 로컬에서 테스트 후 

배포 시에는  OAuth2로 네이버 로그인을 할 경우 

redirect-uri를 배포 주소로 변경해야 합니다!

 

 

 

이후 업데이트

 

OAuth2LoginSuccessHandler를 다음과 같이 변경하여 프론트단으로 JWT토큰을 쿼리스트링에 담아 

리디렉트 되도록 하고 로그인 처리를 하도록 변경했습니다.

이유는 이렇게 발급한 JWT토큰은 프론트단에서 추출하여 사용을 하지 못하기 때문입니다.

 

@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final JwtProvider jwtProvider;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
        Authentication authentication) throws IOException, ServletException {

        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
        String token = jwtProvider.createToken(principalDetails.getUser());
        String email = principalDetails.getEmail();
        String name = principalDetails.getUsername();

        //코드 내로 리디렉트 설정
        //String redirectUrl = "/user/oauth-success?token="+token;

        //한국어 인코딩 설정
        String encodedName = URLEncoder.encode(name, StandardCharsets.UTF_8.toString());
        
        String redirectUrl = "프론트도메인?token=" + token
        +"&email="+email+"&name="+encodedName;
        
        getRedirectStrategy().sendRedirect(request, response, redirectUrl);
    }
}

 

 

callback url에 프론트 도메인을 추가하여 줍니다.

프론트단으로 리디렉트 돼서 로그인을 처리할 것이기 때문입니다.

 

로그인 화면에서 네이버로 시작하기를 클릭 후

 

로그인 창이 뜨면 로그인을 하면

 

 

최초 로그인시 정보 제공을 동의하는지 나옵니다.

동의하면 정보가 제공되어 DB에 정보가 저장되게 됩니다.

 

그 후 로그인 처리가 되어 메인페이지로 온 것을 볼 수 있습니다.

 

 

내 정보 조회를 하면 네이버 OAuth로 로그인한 정보가 확인 가능하네요.

 

 

전체적인 과정을 정리하면 아래 이미지와 같습니다.

Comments