+ 00 00 0000

Have any Questions?

Multi-provider social login sample

Multi-provider social login sample

📃 요약

Multi-provider social login 샘플입니다.
구글 로그인 , 네이버 로그인을 이용해서 진행해 보도록 하겠습니다.
구글 클라우드에 API 신청 및 clientId, clientSecret 로 springboot 에 설정합니다.
마찬가지로, 네이버 디벨로퍼 사이트에서 API 신청 및 clientId, clientSecret 로 springboot 에 설정합니다.

소셜 로그인할 때 DB 에 소셜로그인 사용자를 저장해 보겠습니다.
다중 공급자일때는 어떻게 구현하는지를 보시면 되겠습니다.

글에 언급되지 않는 일반 로그인 소스가(Spring Security) 포함되어 있습니다.

구글 / 네이버 로그인 등록은 생략하겠습니다.
구글 / 네이버 로그인 등록에 관한 것은 아래 주소를 참고하세요
( 참조 블로그 : https://deeplify.dev/back-end/spring/oauth2-social-login )

프론트는 Vue 를 사용하고, 벡엔드는 spring boot 를 사용합니다.

DB 는 오라클 도커 이미지를 사용하고 계정은 scott ( 암호 : !Ds1234567890 ) 개발자 계정을 생성하고 사용합니다.
DB 개발자 계정 및 설치하는 방법은 생략합니다.

요소 기술 :

– 프론트엔드 : Vue

– 벡엔드 : Spring Boot & JPA

– DB : Oracle 18xe(Docker)
( Oracle 18xe 도커 이미지 주소 : https://hub.docker.com/r/kangtaegyung/oraclexe-18c )

결과 화면 :

  • 로그인 화면
  • 구글 계정 선택 화면 #1
  • 구글 계정 선택 화면 #2
  • Home 화면 이동
  • 구글 로그인 완료 후 새로운 구글 사용자 테이블에 저장됨

프로젝트 탐색기 : Vue

프로젝트 탐색기 : String Boot

Front : 소셜 로그인 url 정보

메소드URL설명
GEThttp://localhost:8000/oauth2/code/googleLoginView.vue
구글 로그인 버튼 클릭시 실행될 주소
구글 클라우드에서 등록된 승인된 리다이렉트 URL 주소
GET/auth-redirectSocialRedirectView.vue
벡엔드에서 보내준 웹토큰, 유저정보를
로컬스토리지에 저장하고 동시에 Home 으로 강제 이동함

Backend : 소셜 로그인 URL 정보

메소드URL설명
GEThttp://localhost:8080/auth-redirectSocialLoginSuccess.java
소셜로그인 성공후 Vue 리다이렉트 주소로 웹토큰, 유저정보 전송할 URL 정보

소셜 로그인 간단 절차

  • 1) 로그인 버튼 클릭 : 소셜 로그인 사이트에서 제공된 URL 입니다.
    • 구글 : http://벡엔드주소/oauth2/authorization/google
    • 네이버 : http://벡엔드주소/oauth2/authorization/naver
  • 2) 로그인 자동 인증 : 스프링 OAuth 패키지가 소셜 로그인 공급자에게 받은 인가코드로 인증 토큰을 자동적으로 받아옵니다.
  • 3) 인증 토큰을 받은 후 추가 로직을 개발자가 구현합니다.
    • DB 에 새 사용자 저장
    • Vue 로 웹토큰(JWT) , 사용자 부가 정보 전송

📃 기술 구현

스펙 :

Vue 3.x
구글 API ( 참조 : https://console.cloud.google.com/apis/dashboard?project=simpledms-371707 )
네이버 API ( 참조 : https://developers.naver.com/products/login/api/api.md )
jdk 17
spring boot 3.x
intellij IDEA & gradle
logging tool : logback

테이블 설계

DROP TABLE TB_MEMBER CASCADE CONSTRAINTS;

CREATE TABLE TB_MEMBER
(
    EMAIL       VARCHAR2(1000) NOT NULL PRIMARY KEY, -- id (email)
    PASSWORD    VARCHAR2(1000),                                         -- 암호
    NAME        VARCHAR2(1000),                                         -- 유저명
    CODE_NAME   VARCHAR2(1000),                                         -- 권한코드명(ROLE_USER, ROLE_ADMIN)
    DELETE_YN   VARCHAR2(1) DEFAULT 'N',
    INSERT_TIME VARCHAR2(255),
    UPDATE_TIME VARCHAR2(255),
    DELETE_TIME VARCHAR2(255)
);

소셜 로그인 구현을 위한 테이블 설계입니다.

데이터베이스를 오라클(Docker)을 사용하여 구현해 보겠습니다.

Spring build.gradle : dependencies 블럭 내 추가

dependencies {
    //    OAUTH2 라이브러리 추가 : 소셜 로그인 등 클라이언트 입장에서 소셜 기능 구현 시 필요한 의존성
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
        ... 생략
}

Spring Security 설정 클래스

-WebSecurityConfig.java

package org.example.simpledms.config;

import jakarta.servlet.DispatcherType;
import lombok.RequiredArgsConstructor;
import org.example.simpledms.security.oauth.SocialLoginSuccess;
import org.example.simpledms.security.oauth.SocialLoginServiceCustom;
import org.example.simpledms.security.jwt.AuthTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @fileName : WebSecurityConfig
 * @author : GGG
 * @since : 2024-04-15
 * description :
 *  1) DB 인증을 위한 함수    : passwordEncoder()
 *  2) 패스워드 암호화 함수     : 필수 정의
 *    @Bean : IOC (스프링이 객체를 생성해주는 것), 함수의 리턴객체를 생성함
 *      => (참고) 용어 : 스프링 생성한 객체 == 빈(Bean==콩)
 *  3) JWT 웹토큰 자동인증 함수 : authenticationJwtTokenFilter()
 *  4) img, css, js 등 인증 무시 설정 함수 : webSecurityCustomizer()
 *      => 사용법 : (web) -> web.ignoring().requestMatchers("경로", "경로2"...)
 *  5) 스프링 시큐리티 규칙 정의 함수(***) : filterChain(HttpSecurity http)
 *    5-1) cors 사용
 *    5-2) csrf 해킹 보안 비활성화(쿠키/세션 사용않함)
 *    5-3) 쿠키/세션 안함(비활성화) -> 로컬스토리지/웹토큰
 *    5-4) form 태그 action 을 이용한 로그인 사용않함 -> axios 통신함
 *    5-5) /api/auth/**  : 이 url 은 모든 사용자 접근 허용, ** (하위 url 모두 포함)
 *    5-8) / : 이 url 은 모든 사용자 접근 허용
 *    5-9) TODO : 웹토큰 클래스를 스프링시큐리티 설정에 끼워넣기 : 모든 게시판 조회(CRUD)에서 아래 인증을 실행함
 *
 *  6) 소셜 로그인
 *    6-1) 소셜 로그인 성공후 처리할 리다이렉션해서 구글 인가코드 받음
 *    6-2) 구글 인가코드 확인 후에 DB 인증, 웹토큰 발행, 프론트로 전송
 */
@Configuration
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final SocialLoginSuccess socialLoginSuccess;

    private final SocialLoginServiceCustom socialLoginServiceCustom ;

    private final AuthTokenFilter authTokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

//  img, css, js 등 인증 무시 설정 함수
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers(
                "/img/**",
                "/css/**",
                "/js/**"
        );
    }

    //    TODO: 스프링 시큐리티 규칙 정의 함수(***)
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.cors(Customizer.withDefaults());                         // 5-1)
        http.csrf((csrf) -> csrf.disable());                          // 5-2)
        http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 5-3)
        http.formLogin(req -> req.disable());                         // 5-4)

        http.authorizeHttpRequests(req -> req
                .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
                .requestMatchers("/api/auth/**").permitAll()        // 5-5)
                .requestMatchers("/").permitAll()                   // 5-8)
                .anyRequest()
                .authenticated());

//        TODO: 소셜 로그인 설정 부분
        http.oauth2Login(req  -> req
                .successHandler(socialLoginSuccess)                                     // 6-1)
                .userInfoEndpoint(arg  -> arg.userService(socialLoginServiceCustom))    // 6-2)
        );

//        5-9)
        http.addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}
  • TODO: 소셜 로그인 설정 부분이 Spring Security 에서 설정할 부분입니다.
  • socialLoginSuccess : 성공후에 실행될 클래스이고, Vue 로 웹토큰 , 유저정보를 전송합니다.
  • socialLoginServiceCustom : 구글, 네이버 인증토큰을 받으면 실행될 클래스 이고, DB 에 새로운 소셜 사용자를 등록합니다.

소셜 로그인 클래스 :  구글 API 인가토큰을 받아 인증 처리 하는 클래스

-SocialLoginServiceCustom.java

package org.example.simpledms.security.oauth;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.simpledms.model.entity.auth.Member;
import org.example.simpledms.repository.auth.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import java.util.Collections;

/**
 * @fileName : SocialLoginServiceCustom
 * @author : kangtaegyung
 * @since : 2022/12/16
 * description :
 * 알고리즘
 * 1) OAuth2UserService : 유저정보 있는 클래스
 * 2) registrationId : google, naver, kakao 같은 이름이 있음, 이것으로 각 서비스를 구분함
 * 3) OAuth2 로그인 진행시 키가 되는 필드값, 인증토큰(PK 와 같음)
 * 4) registrationId 에 따라 구글함수, 네이버함수, 카카오함수를 실행하는 함수
 * 5) 소셜 기본정보 DB 저장, 유저가 있으면 무시
 * 6) 소셜유저 생성 및 내보내기
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SocialLoginServiceCustom implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final MemberRepository memberRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> socialLoginService = new DefaultOAuth2UserService();              // 1)
        OAuth2User socialLogin = socialLoginService.loadUser(userRequest);

        String registrationId = userRequest.getClientRegistration().getRegistrationId();                                   // 2)
        String socialKey = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName();                                                         // 3)

        SocialProviders socialProviders = SocialProviders.of(registrationId, socialKey, socialLogin.getAttributes());      // 4)

        saveSocialIdOrSkip(socialProviders);                                                                               // 5)

        return new DefaultOAuth2User(                                                                                      // 6)
                Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
                socialProviders.getSocialUser(),
                socialProviders.getSocialKey());
    }

    private void saveSocialIdOrSkip(SocialProviders socialProviders) {
        try {
            if(memberRepository.existsById(socialProviders.getEmail()) == false) {
                memberRepository.save(socialProviders.getDefaultUser());
            }
        } catch (Exception e) {
            log.debug("saveSocialIdOrSkip 에러" ,e.getMessage());
        }
    }
}
  • 구글 / 네이버 로그인 성공 후 인증토큰을 받으면(자동) 벡엔드에서 새로운 사용자로 DB 에 저장합니다.
    • 구글, 네이버 인가코드를 통한 인증토큰은 OAuth 스프링 라이브러리가 자동적으로 처리하고 인증토큰을 받아옵니다
    • 개발자는 구글 / 네이버 로그인이 정상적으로 진행되었다는 가정하에 (인증토큰 받음) 추가 로직을 작성하면 됩니다. 여기서는 DB 에 사용자를 저장합니다.
  • DB 에 저장시 기본정보로 저장합니다.

– SocialLoginSuccess.java

DB 에 사용자 저장 후에 실행될 클래스 : 웹토큰 발급 및 프론트로 유저 정보 전송
package org.example.simpledms.security.oauth;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.simpledms.security.jwt.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
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.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @fileName : SocialLoginSuccess
 * @author : kangtaegyung
 * @since : 2022/12/16
 * description : 소셜 로그인 성공 후 처리할 클래스
 * 알고리즘
 * 1) 인증된 객체를 홀더에 저장
 * 2) 인증된 유저 정보를 oAuth2User(소셜로그인 클래스) 에 저장, 소셜로그인은 oAuth2User 사용
 * 3) 권한 정보 가져오기
 * 4) provider 정보 : google, naver, kakao
 * 5) 구글/네이버 등 provider 마다 전달해주는 속성명과 구조가 틀림
 *     구글 : { email: forbob@naver.com , name: 강태경 }
 *     네이버 : { response : { email: forbob@naver.com , id : abcdef } }
 * 6) 토큰 발행
 * 7) 리다이렉션 페이지로 이동 : vue 로 jwt , 유저정보를 전송함
 * <p>
 * 참고) 함수
 * - UriComponentsBuilder.fromUriString("기본url")
 * .queryParam("키", 값)    // 쿼리스트링 변수명, 값
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SocialLoginSuccess extends SimpleUrlAuthenticationSuccessHandler {
    private final JwtUtils jwtUtils;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException {
        SecurityContextHolder.getContext().setAuthentication(authentication);                        // 1)
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();                          // 2)

        List<GrantedAuthority> authorities = new ArrayList(authentication.getAuthorities());
        String codeName = authorities.get(0).toString();                                             // 3) 권한

        String email = "";

        OAuth2AuthenticationToken authToken = (OAuth2AuthenticationToken) authentication;            // 4) provider 정보

        switch (authToken.getAuthorizedClientRegistrationId()) {                                     // 5)
            case "google":
                Map<String, Object> googleUser = oAuth2User.getAttributes();
                email = (String) googleUser.get("email");
                break;
            case "naver":
                Map<String, Object> naverUser = (Map<String, Object>) oAuth2User.getAttributes().get("response");
                email = (String) naverUser.get("email");
                break;
        }

        String jwt = jwtUtils.generateJwtToken(email);                                                // 6)

        String targetUrl = UriComponentsBuilder.fromUriString("http://localhost:8080/auth-redirect")  // 7)
                .queryParam("accessToken", jwt)
                .queryParam("email", email)
                .queryParam("codeName", codeName)
                .build().toUriString();

        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
}
  • DB 에 새사용자가 저장되면 웹토큰을 발급해서 프론트로 유저정보와 함께 전송합니다.
    • 프론트 전송 주소는 http://localhost:8080/auth-redirect 입니다.
    • 웹토큰, email, codeName(권한) 정보를 쿼리스트링 방식으로 전송합니다.

– SocialProviders

구글 / 네이버 로그인 ( Providers ) 에 따라 해당되는 함수가 실행될 수 있도록 작성된 클래스
package org.example.simpledms.security.oauth;

import lombok.Builder;
import lombok.Getter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.example.simpledms.model.entity.auth.Member;

import java.util.Map;

/**
 * @fileName : SocialProviders
 * @author : kangtaegyung
 * @since : 2022/12/16
 * description : name 는 email 의 @ 앞부분을 잘라서 저장
 * 각각 : email 저장된 속성명
 * google : (String) socialUser.get("email")
 * naver  :  Map<String, Object> response = (Map<String, Object>) socialUser.get("response") 안에 email 속성이 있음
 * (String) response.get("email")
 * kakao  : (String) socialUser.get("account_email")
 */
@Getter
public class SocialProviders {
    PasswordEncoder encoder = new BCryptPasswordEncoder();
    private Map<String, Object> socialUser;
    private String socialKey;
    private String name;
    private String email;

    @Builder
    public SocialProviders(Map<String, Object> socialUser,
                           String socialKey, String name,
                           String email) {
        this.socialUser = socialUser;
        this.socialKey = socialKey;
        this.name = name;
        this.email = email;
    }

    //    registrationId 를 체크해서 구글 / 네이버 / 카카오 정보 가져오기
    public static SocialProviders of(String registrationId,
                                     String nameAttributeName,
                                     Map<String, Object> socialUser) {
        switch (registrationId) {
            case "google":
                return ofGoogle(nameAttributeName, socialUser);
            case "naver":
                return ofNaver(nameAttributeName, socialUser);
            case "kakao":
                return ofKakao(nameAttributeName, socialUser);
            default:
                return ofGoogle(nameAttributeName, socialUser);
        }
    }

    private static SocialProviders ofGoogle(String nameAttributeName,
                                            Map<String, Object> socialUser) {
        return SocialProviders.builder()
                .name(((String) socialUser.get("email")).split("@")[0])
                .email((String) socialUser.get("email"))
                .socialUser(socialUser)
                .socialKey(nameAttributeName)
                .build();
    }

    private static SocialProviders ofNaver(String nameAttributeName,
                                           Map<String, Object> socialUser) {
        Map<String, Object> response = (Map<String, Object>) socialUser.get("response");

        return SocialProviders.builder()
                .name(((String) response.get("email")).split("@")[0])
                .email((String) response.get("email"))
                .socialUser(socialUser)
                .socialKey(nameAttributeName)
                .build();
    }

    private static SocialProviders ofKakao(String nameAttributeName,
                                           Map<String, Object> socialUser) {
        Map<String, Object> kakaoAccount = (Map<String, Object>) socialUser.get("kakao_account");

        return SocialProviders.builder()
                .name(((String) kakaoAccount.get("email")).split("@")[0])
                .email((String) kakaoAccount.get("email"))
                .socialUser(socialUser)
                .socialKey(nameAttributeName)
                .build();
    }

    //    OAuthsocialUser에서 엔티티를 생성하는 시점은 처음 가입할 때
    //    User 엔티티를 생성
    public Member getDefaultUser() {

        return Member.builder()
                .name(this.email)
                .email(this.email)
                .password(encoder.encode("123456"))
                .codeName("ROLE_USER")
                .build();
    }
}
  • registrationId : 구글 인지 네이버 공급자( Providers ) 인지 알려주는 변수입니다.
    • 공급자가 인증토큰과 함께 전송해 줍니다.
    • 저장된 값은 google 또는 naver 가 저장되어 있습니다.

Vue 페이지 :

프론트는 로그인 버튼에 네이버 인증 요청 링크를 추가하는 것 외에 싱글 공급자 로그인 예제와 거의 동일합니다.

– Vue 패키지 추가 :

npm i axios

1) 공통 js

– utils/axiosDefaultConfig.js : axios 기본 설정 파일

import axios from "axios";

// axios 기본 설정
export default axios.create({
  baseURL: "http://localhost:8000/api",
  headers: {
    "Content-Type": "application/json"
  }
});

– router/index.js

import { createRouter, createWebHistory } from "vue-router";

const routes = [
  {
    path: "/",
    component: () => import("../views/HomeView.vue"),
  },
  // 로그인
  {
    path: "/login",
    component: () => import("../views/auth/LoginView.vue"),
  },
  // 회원가입
  {
    path: "/register",
    component: () => import("../views/auth/RegisterView.vue"),
  },
  // 소셜 로그인
  {
    path: "/auth-redirect",
    component: () => import("../views/auth/SocialRedirectView.vue"),
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

Vue 페이지

로그인 결과 화면 :

– views/auth/LoginView.vue

<!-- 사용법 : @submit.prevent="함수" -->
<!--       prevent : submit 의 기본 속성을 막기(다른 곳으로 이동하려는 특징)  -->

<template>
  <div>
    <div class="row justify-content-center">
      <div class="col-xl-10 col-lg-12 col-md-9">
        <div class="card mt-5">
          <div class="card-body p-0">
            <!-- {/* Nested Row within Card Body */} -->
            <div class="row">
              <div class="col-lg-6 bg-login-image"></div>
              <div class="col-lg-6">
                <div class="p-5">
                  <div class="text-center">
                    <h1 class="h4 mb-4">Welcome Back!</h1>
                  </div>
                  <form class="user" @submit.prevent="login">
                    <div class="form-group">
                      <input
                        type="email"
                        class="form-control form-control-user mb-3"
                        placeholder="이메일을 넣기"
                        name="email"
                        v-model="user.email"
                      />
                    </div>
                    <div class="form-group">
                      <input
                        type="password"
                        class="form-control form-control-user mb-3"
                        placeholder="패스워드 넣기"
                        name="password"
                        v-model="user.password"
                      />
                    </div>

                    <button class="btn btn-primary btn-user w-100 mb-3">
                      Login
                    </button>
                    <hr />
                    <a
                      href="http://localhost:8000/oauth2/authorization/google"
                      class="btn btn-google btn-user w-100 mb-2"
                    >
                      <i class="fab fa-google fa-fw"></i>&nbsp;Login with Google
                    </a>
                    <a href="http://localhost:8000/oauth2/authorization/naver" class="btn btn-naver btn-user w-100 mb-2">
                      <i class="fa-solid fa-n"></i>&nbsp;Login with Naver
                    </a>
                    <a href="/" class="btn btn-kakao btn-user w-100 mb-3">
                      <i class="fa-solid fa-k"></i>&nbsp;Login with Kakao
                    </a>
                  </form>
                  <hr />
                  <div class="text-center">
                    <a class="small" href="/forgot-password">
                      Forgot Password?
                    </a>
                  </div>
                  <div class="text-center">
                    <a class="small" href="/register"> Create an Account! </a>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
// TODO: 1) spring 보내준 user 객체(웹토큰있음)를 로컬스토리지에 저장
// TODO:   사용법 :  localStorage.setItem(키, 값);
// TODO:     => 단, 값은 문자열만 저장됨
// TODO:   사용법 : JSON.stringify(객체) => 문자열로 바뀐 객체가 리턴됨

// TODO: 2) 공유저장소의 state / mutations 함수 접근법
// TODO:   mutations 사용법 : this.$store.commit("함수명", 저장할객체)
// TODO:     => 로그인성공 공유함수(loginSuccess(state, 유저객체)) 실행
// TODO:   state 사용법 : this.$store.state.공유속성명
// TODO:     => 공유저장소의 공유속성 접근법

// TODO: 3) 뷰의 라이프사이클
// TODO:   - mounted() : 화면이 뜰때 자동 실행 (생명주기 함수)
// TODO:   - created() : 뷰가 생성될대 자동 실행
// TODO:   - created()(1번, 뷰만 생성되면 실행) -> mounted()(2번, html 태그까지 모두 뜰때)
// TODO:     예) destoryed() : 뷰가 삭제될때 실행 (거의 사용 않함)

import AuthService from "@/services/auth/AuthService";
export default {
  data() {
    return {
      user: {
        email: "", // 로그인ID
        password: "",
      },
    };
  },
  methods: {
    async login() {
      try {
        let response = await AuthService.login(this.user);
        console.log(response.data);

        localStorage.setItem("user", JSON.stringify(response.data)); // 1)

        this.$store.commit("loginSuccess", response.data); // 2)

        this.$router.push("/");
      } catch (e) {
        this.$store.commit("loginFailure");

        console.log(e);
      }
    },
  },
  // 화면이 뜰때 실행되는 함수
  created() {
    if (this.$store.state.loggedIn == true) {
      // 로그인 상태이면 로그인 불필요
      this.$router.push("/");
    }
  },
};
</script>
<style>
@import "@/assets/css/login.css";
</style>
  • 로그인 버튼 url : 구글 API 주소 (http://localhost:8000/oauth2/code/google)
  • 상기 주소를 구글 로그인 API 신청 시 클라우드 사이트에 등록해야 합니다.

Vue 리다이렉트 페이지

– views/auth/SocialRedirectView.vue

<!-- 소셜로그인 리다이렉트 페이지 -->
<template>
  <div >
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {},
    };
  },
  mounted() {
    let url = new URL(window.location.href);
    console.log(url);

    const urlParams = url.searchParams;                // uri 정보 가져오기
    const accessToken = urlParams.get("accessToken");
    const email = urlParams.get("email");
    const codeName = urlParams.get("codeName");

    this.user = {
      accessToken: accessToken,
      email: email,
      codeName: codeName,
    };

    console.log("social user", this.user);
    
    localStorage.setItem("user", JSON.stringify(this.user));
    this.$store.commit('loginSuccess', this.user);

    this.$router.push("/");
  },
};
</script>
  • 벡엔드에서 소셜 로그인 성공하면 Vue. 로 웹토큰, 유저정보를 보내줍니다.
  • 로컬스토리지에 유저정보를 저장하고, Home 으로 강제 페이지 이동 시킵니다.

📃 결론

구글 / 네이버 로그인을 이용해서 소셜 로그인을 진행하는 예제를 살펴보았습니다.

구글 로그인 버튼을 클릭하면 구글 로그인 url 로 이동되고, 네이버 로그인 버튼을 클릭하면 네이버 로그인 url 로 이동됩니다.
구글 / 네이버 인증이 완료되면 인증토큰을 springboot 으로 전송합니다.

인가코드 및 인증토큰을 받는 로직은 OAuth 스프링 패키지가 자동적으로 처리합니다.
개발자는 인증토큰을 받은 후에 새 사용자를 DB 에 생성하고 웹토큰을 vue 전송하는 로직만 작성합니다.

Spring Boot 는 @RestController 어노테이션을 이용해 구현했으며, 결과는 JSON 데이터로 리턴됩니다.
Vue 는 axios 라이브러리를 사용해 벡엔드와 통신합니다.

DB 프레임워크는 JPA 를 이용해서 sql 문을 직접 제작하지 않고 자동화기능을 이용해 구현했습니다.

다중 로그인에 관심 있으시다면 Source 는 아래에서 찾을 수 있습니다.

감사합니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다