///
Search
👥

OAuth2.0 프로토콜을 사용한 SSO 구현기 - 코드 생산성을 높이자!

태그
spring boot 3
OAuth2.0
SSO
vue.js
분류
Spring Boot
목차

Rest Api 로 구현한 소셜 로그인 동작 과정

1. 로그인 화면 제공

사용자가 구글 계정 정보를 입력할 수 있는 로그인 폼을 제공합니다.
이 과정에서 프론트엔드는 사용자의 이메일과 비밀번호를 수집하여 백엔드로 전달합니다.

2. 자격 증명 전송

백엔드는 받은 정보를 사용하여 구글 서버에 직접 인증 요청을 보냅니다.
구글 서버로부터 응답을 받아 사용자가 유효한지 확인합니다.

3. 토큰 발급

구글 서버로부터 유효한 자격 증명이라는 것이 확인되면, 인증 토큰을 생성하여 클라이언트에 반환합니다.
이 토큰을 사용하여 이후 API 요청 시 사용자 인증을 처리합니다.

4. 데이터 저장 및 관리

인증이 성공하면 사용자의 정보를 데이터베이스에 저장하거나 세션을 관리합니다.
이렇게 RES Api 로 구현해도 되지만 아래와 같은 단점이 있습니다.
프론트가 중간에 끼어들게 되면서 비밀번호가 노출될 가능성이 있습니다.
사실 이것 만으로도 큰 리스크 입니다.
카카오 API 는 OAuth2.0 기반이라 괜찮긴 합니다.
소셜 로그인마다 토큰 발급 요청 시 보내야 하는 내용이 다릅니다.
유지 보수에 용이하지 않으며 효율성이 떨어집니다.
그래서 저는 OAuth2.0 프로토콜을 사용하여 SSO 를 구현하였습니다.

SSO(Single sign-on) 가 뭔데요?

SSO 가 있는 경우와 없는 경우를 도식화한 그림입니다. 벌써 부터 울고 있는데요
SSO (Single sign-on) 의 줄임말 입니다. 말 그대로 하나의 통합 인증을 거쳐 여러 시스템을 사용할 수 있도록 하는 것이죠. 만약 중간에 길을 알려주는 역할을 하는 단계 없다면 어떻게 될까요?
여러 소셜 로그인을 구현해야 하는 경우를 생각해봅시다. 카카오, 네이버, 구글 등 다양한 소셜 정보를 이용하여 사용자 인증을 마치고 싶지만 SSO 로 구현하지 않는다면 위와같이 REST Api 를 이용해 각 소셜 서비스 별로 Service 를 만들어야 합니다. 위에서 언급했듯이 각 소셜 서버마다 인증을 위해 요구하는 내용이 다르고, 인증방식이 다르기 때문에 확장성이 떨어지며, 유지보수가 용이하지 않죠.
SSO 를 사용하면 어떨까요? SSO 의 경우 유저 정보를 중앙에서 관리하기 때문에 효율성이 높아집니다. 맨처음 진입점이 되며, 이후의 길을 알려주는 안내자가 되는 셈이죠. 이를 구현하기 위해서는 OAuth2.0 프로토콜을 사용합니다.

SSO 구현에 OAuth2.0 이 어떤 역할을 하는 걸까요?

OAuth2.0은 SSO 를 구현하는 방법 중의 하나로 인증을 위한 개방형 표준 프로토콜입니다. 이를 통해 사용자의 인증 정보를 관리하고, 다양한 서비스에 접근할 수 있는 권한을 부여할 수 있습니다.

Spring Security 와 OAuth 2.0 의 통합

스프링 시큐리티는 OAuth 2.0 을 쉽게 통합할 수 있는 기능을 제공합니다. 이를 통해 소셜 로그인을 간편하게 구현할 수 있습니다. 다양한 기능을 제공하지만 여기서는 스프링 시큐리티 OAuth2 client 에 대해서만 이야기하겠습니다. 이를 사용하면 application 이 OAuth 2.0 제공자 즉, 구글, 페이스북 등에 요청을 보내고 인증을 처리한 후 토큰을 받아올 수 있습니다. 그리고 받아온 정보를 이용하여 회원가입 → 로그인 처리를 한 후 Spring Security 를 사용해 프로그램의 다양한 서비스에 대한 인증 인가 절차를 밟을 수 있죠.

개인 정보 관리에 대한 책임 위임

OAuth2.0 을 사용하면 로그인이나 개인 정보 관리에 대한 책임을 ‘Third-Party Application’에 위임합니다. 로그인과 관련된 보안을 카카오와 구글에 맡기는겁니다. 이들이 인증 정보를 관리하고 보호해주기 때문에 사용자의 비밀번호나 민감 정보를 저장하거나 처리할 필요가 없습니다. 따라서 보안 위험이 크게 줄어들게 됩니다.

OAuth 2.0 프로토콜을 사용한 소셜 로그인 동작 과정

1. OAuth 클라이언트 설정

구글 API 콘솔에서 프로젝트를 생성하고, 해당 프로젝트에 대한 OAuth 클라이언트를 설정합니다.
발급받은 client ID 와 client secret key 는 백엔드에서 사용합니다.

2. 구글 로그인 버튼 노출

구글 계정으로 로그인할 수 있는 버튼을 제공합니다.
버튼을 클릭하면 사용자는 구글의 OAuth2.0 인증 페이지로 redirect 됩니다.

3. 구글 인증 페이지로 Redirect

이때 구글 인증 요청 URL 에 client ID, scope, redirect URI 등을 넣어야 합니다.

4. 인증 후 Redirect

사용자가 인증을 마치면 구글 server 는 인증 코드를 frontend 에 전달합니다.
이때 미리 지정된 redirect URI 로 사용자를 redirect 합니다.
이 URI 에는 인증 코드가 쿼리 파라미터로 포함되어 있습니다.

5. 인증 코드 수신 및 처리

프론트엔드에서 구글이 전송한 인증코드를 백엔드에 전달합니다.
백엔드는 이 인증코드를 사용하여 구글의 OAuth server 에 access Token 을 요청합니다.

6. 토큰 교환

백엔드는 구글 OAuth 서버에 access code, client ID, client secret 등을 포함한 요청을 보냅니다.
구글 서버는 이 요청을 검증한 후, access token 과 refresh token 을 백엔드에 응답으로 보냅니다.

7. 사용자 정보 요청

백엔드는 access token 을 사용하여 구글 API 에 사용자 정보를 다시 요청합니다.
사용자의 이메일, 이름 등 기본 정보를 받아와서 처리합니다.

8. 토큰관리

백엔드는 access token 과 refresh token 을 관리하며, access token 이 만료되면 refresh token 을 사용하여 새로운 access token 을 발급 받습니다.
사용자가 다시 로그인을 할 필요없이 자동으로 토큰을 갱신할 수 있습니다.

소셜 로그인 구현

build.gradle

아래와 같이 dependency 를 추가해줍니다.
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
Plain Text
복사

application.yml

구글과 카카오에서 발급받은 key 들을 알맞게 넣어줍니다.
spring: security: oauth2: client: registration: google: redirect-uri: ${GOOGLE_REDIRECT_URL} client-id: ${GOOGLE_CLIENT_ID} client-secret: ${GOOGLE_CLIENT_SECRET} scope: - email - profile kakao: client-id: ${KAKAO_REST_API_KEY} redirect-uri: ${KAKAO_REDIRECT_URL} client-secret: ${KAKAO_SECRET_KEY} client-name: Kakao authorization-grant-type: authorization_code client-authentication-method: client_secret_post scope: - account_email - profile_nickname - profile_image provider: kakao: authorization-uri: https://kauth.kakao.com/oauth/authorize token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id
YAML
복사

CustomOAuth2User.java

OAuth2User 를 커스텀한 코드입니다. provider 를 통해 어느 소셜 로그인 계정인지를 구분하고, 알맞게 사용자 정보를 가져옵니다. 소셜 로그인마다 가져오는 방식이 약간씩 다르기에 구분해주는 과정이 필요합니다.
import com.gudgo.jeju.domain.user.entity.User; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.user.OAuth2User; import java.util.Collection; import java.util.Collections; import java.util.Map; @Getter @RequiredArgsConstructor public class CustomOAuth2User implements OAuth2User { private final User user; private final Map<String, Object> attributes; @Override public <A> A getAttribute(String name) { return OAuth2User.super.getAttribute(name); } @Override public Map<String, Object> getAttributes() { return attributes; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")); } @Override public String getName() { if (user.getProvider().equals("google")) { return attributes.get("name").toString(); } else if (user.getProvider().equals("kakao")) { return ((Map<?, ?>) attributes.get("properties")).get("nickname").toString(); } return null; } }
Java
복사

OAuth2UserInfo.java

다양한 소셜 로그인을 위해 interface 를 만들어 줍니다.
public interface OAuth2UserInfo { String getEmail(); String getName(); String getPassword(); String getProfile(); }
Java
복사

GoogleUserInfo.java

KakaoUserInfo.java

OAuth2UserInfoFactory.java

import lombok.extern.slf4j.Slf4j; import java.util.Map; @Slf4j public class OAuth2UserInfoFactory { public static OAuth2UserInfo getOAuthUserInfo(String provider, Map<String, Object> attributes) { if (provider.equals("google")) { log.info("============================================================="); log.info("Google login Request sent"); log.info("============================================================="); return new GoogleUserInfo(attributes); } else if (provider.equals("kakao")) { log.info("============================================================="); log.info("Kakao login Request sent"); log.info("============================================================="); return new KakaoUserInfo(attributes); } return null; } }
Java
복사

OAuth2SignupService.java

소셜 로그인에서 받아온 정보를 이용해 실제 서비스에 회원가입을 하는 코드입니다. 이는 구현하려는 서비스에 따라 다르니 비즈니스 로직에 맞춰 변경해주세요.
import com.gudgo.jeju.domain.user.entity.User; import com.gudgo.jeju.domain.user.repository.UserRepository; import com.gudgo.jeju.global.auth.oauth.entity.CustomOAuth2User; import com.gudgo.jeju.global.auth.oauth.entity.OAuth2UserInfo; import com.gudgo.jeju.global.auth.oauth.entity.OAuth2UserInfoFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.Optional; @Service @Slf4j @RequiredArgsConstructor public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; private final OAuth2SignupService oAuth2SignupService; @Override public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(request); log.info("============================================================================"); log.info("getAttributes: {}", oAuth2User.getAttributes()); log.info("============================================================================"); String provider = request.getClientRegistration().getRegistrationId(); OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuthUserInfo(provider, oAuth2User.getAttributes()); Optional<User> user = userRepository.findByEmailAndProvider(provider, oAuth2UserInfo.getEmail()); if (user.isPresent()) { return new CustomOAuth2User(user.get(), oAuth2User.getAttributes()); } else { User newUser = oAuth2SignupService.signup(provider, oAuth2UserInfo); return new CustomOAuth2User(newUser, oAuth2User.getAttributes()); } } }
Java
복사

CustomOAuth2UserService.java

import com.gudgo.jeju.domain.user.entity.User; import com.gudgo.jeju.domain.user.repository.UserRepository; import com.gudgo.jeju.global.auth.oauth.entity.CustomOAuth2User; import com.gudgo.jeju.global.auth.oauth.entity.OAuth2UserInfo; import com.gudgo.jeju.global.auth.oauth.entity.OAuth2UserInfoFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.Optional; @Service @Slf4j @RequiredArgsConstructor public class CustomOAuth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; private final OAuth2SignupService oAuth2SignupService; @Override public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(request); log.info("============================================================================"); log.info("getAttributes: {}", oAuth2User.getAttributes()); log.info("============================================================================"); String provider = request.getClientRegistration().getRegistrationId(); OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuthUserInfo(provider, oAuth2User.getAttributes()); Optional<User> user = userRepository.findByEmailAndProvider(provider, oAuth2UserInfo.getEmail()); if (user.isPresent()) { return new CustomOAuth2User(user.get(), oAuth2User.getAttributes()); } else { User newUser = oAuth2SignupService.signup(provider, oAuth2UserInfo); return new CustomOAuth2User(newUser, oAuth2User.getAttributes()); } } }
Java
복사

OAuth2AuthenticationSuccessHandler.java

모든 인증과정을 거치는 데 성공하면 실행되는 코드 입니다. 받은 정보를 가지고 회원가입이 완료되었을 시 강제 로그인 과정을 거칩니다. 그리고 프론트에게 필요한 정보를 request param 으로 건내줍니다.
import com.fasterxml.jackson.databind.ObjectMapper; import com.gudgo.jeju.domain.profile.entity.Profile; import com.gudgo.jeju.domain.profile.repository.ProfileRepository; import com.gudgo.jeju.domain.user.dto.UserInfoResponseDto; import com.gudgo.jeju.domain.user.entity.User; import com.gudgo.jeju.domain.user.repository.UserRepository; import com.gudgo.jeju.global.auth.oauth.entity.CustomOAuth2User; import com.gudgo.jeju.global.jwt.token.TokenGenerator; import com.gudgo.jeju.global.jwt.token.TokenType; import com.gudgo.jeju.global.util.CookieUtil; import com.gudgo.jeju.global.util.RedisUtil; import jakarta.persistence.EntityNotFoundException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @Slf4j @Component @RequiredArgsConstructor public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final ProfileRepository profileRepository; private String PRE_FRONT_REDIRECT_URL = "http://localhost:5173"; private final TokenGenerator tokenGenerator; private final CookieUtil cookieUtil; private final RedisUtil redisUtil; private final UserRepository userRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal(); String provider = customOAuth2User.getUser().getProvider(); User user = userRepository.findById(customOAuth2User.getUser().getId()) .orElseThrow(EntityNotFoundException::new); Profile profile = profileRepository.findById(user.getProfile().getId()) .orElseThrow(EntityNotFoundException::new); UserInfoResponseDto userInfoResponseDto = new UserInfoResponseDto( user.getId(), user.getEmail(), user.getNickname(), user.getName(), user.getNumberTag(), profile.getProfileImageUrl(), user.getRole() ); if (response.isCommitted()) { log.info("============================================================================"); log.info("Social login response has been successfully sent."); log.info("============================================================================"); } log.info("============================================================================"); log.info("Social login successful. Social type is {}", provider); log.info("============================================================================"); String accessToken = tokenGenerator.generateToken(TokenType.ACCESS, String.valueOf(customOAuth2User.getUser().getId())); String refreshToken = tokenGenerator.generateToken(TokenType.REFRESH, String.valueOf(customOAuth2User.getUser().getId())); response.setHeader("Authorization", "Bearer " + accessToken); cookieUtil.setCookie("refreshToken", refreshToken, response); redisUtil.setData(String.valueOf(customOAuth2User.getUser().getId()), refreshToken); String frontendRedirectUrl = setRedirectUrl(accessToken, userInfoResponseDto); response.sendRedirect(frontendRedirectUrl); } private String setRedirectUrl (String accessToken, UserInfoResponseDto userInfoResponseDto) { String encodedUserId = URLEncoder.encode(String.valueOf(userInfoResponseDto.id()), StandardCharsets.UTF_8); String encodedEmail = URLEncoder.encode(String.valueOf(userInfoResponseDto.email()), StandardCharsets.UTF_8); String encodedNickname = URLEncoder.encode(String.valueOf(userInfoResponseDto.nickname()), StandardCharsets.UTF_8); String encodedName = URLEncoder.encode(String.valueOf(userInfoResponseDto.name()), StandardCharsets.UTF_8); String encodedNumberTag = URLEncoder.encode(String.valueOf(userInfoResponseDto.numberTag()), StandardCharsets.UTF_8); String encodedProfiIeImageUrl = URLEncoder.encode(String.valueOf(userInfoResponseDto.profileImageUrl()), StandardCharsets.UTF_8); String encodedUserRole = URLEncoder.encode(String.valueOf(userInfoResponseDto.userRole()), StandardCharsets.UTF_8); String frontendRedirectURL = String.format( "%s/oauth/callback?token=%s&userId=%s&email=%s&nickname=%s&name=%s&numberTag=%s&profileImageUrl=%s&userRole=%s", PRE_FRONT_REDIRECT_URL, accessToken, encodedUserId, encodedEmail, encodedNickname, encodedName, encodedNumberTag, encodedProfiIeImageUrl, encodedUserRole ); return frontendRedirectURL; } }
Java
복사

SecurityConfiguration.java

소셜 로그인 관련 url 가 security 필터를 거치지 않도록 설정해줍니다.
위에서 작성한 서비스와 핸들러를 등록해줍니다.
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity .csrf(AbstractHttpConfigurer::disable) .cors(cors -> {}) .authorizeHttpRequests((authorizeRequest) -> authorizeRequest ... .requestMatchers("/oauth/**").permitAll() .requestMatchers("/api/v1/oauth/**").permitAll() ... .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(authorization -> authorization .baseUri("/api/v1/oauth/authorize") ) .redirectionEndpoint(redirection -> redirection .baseUri("/oauth/callback") ) .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) ) .successHandler(oAuth2AuthenticationSuccessHandler) ); return httpSecurity.build();
Java
복사

JWTAuthenticationFilter.java

소셜 로그인 관련 url 가 jwt 필터를 거치지 않도록 설정해줍니다.
import com.gudgo.jeju.global.jwt.token.TokenAuthenticator; import com.gudgo.jeju.global.jwt.token.TokenExtractor; import com.gudgo.jeju.global.jwt.token.TokenValidator; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @RequiredArgsConstructor public class JWTAuthenticationFilter extends OncePerRequestFilter { private final TokenExtractor tokenExtractor; private final TokenValidator tokenValidator; private final TokenAuthenticator tokenAuthenticator; @Override protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { String requestURI = request.getRequestURI(); if ( requestURI.startsWith("/api/v1/auth") || requestURI.startsWith("/api/v1/auth/token") || requestURI.startsWith("/api/v1/oauth") || ...
Java
복사

주의점

구글의 경우 도메인을 등록하지 않으면 개발자 계정으로 등록된 유저만 정상적으로 테스트가 됩니다.

실제 구동 순서는 어떻게 될까?

해당 과정은 보이지 않는 인증 과정을 거치고, 인증이 완료된 후 받은 프로필 정보를 처리하는 과정입니다. 더 자세한 내용은 코드를 뜯어보고 알려드릴게요!

1. 소셜 로그인 요청

/api/v1/oauth/authorize/… 을 통해 소셜 로그인 요청을 보냅니다.
SecurityConfiguration.java 에 설정한대로 OAuth2 로그인 프로세스가 시작됩니다.

2. OAuth2 로그인

OAuth2 공급자가 제공한 화면에서 로그인을 합니다.
성공하면 콜백 url 로 인증 정보가 옵니다.

3. OAuth2User 정보 로드

CustomOAuthUserService.java 의 loadUser() 메서드를 호출합니다.
DefaultOAuth2UserService 의 loadUser() 메서드가 호출되어 OAuth2 프로필 정보를 로드합니다.

4. OAuth2UserInfo Factory pattern 적용

OAuth2UserInfoFactory.java 에서 공급자에 따라 적절한 OAuth2UserInfo 를 반환합니다.
이를 위해 GoogleUserInfo.java, KakaoUserInfo.java 를 만들었으며, 이를 통해 provider 에 따른 데이터 구조를 다를 수 있습니다.

5. 회원가입 처리

CustomOAuth2UserService.java 의 OAuth2UserInfo 로부터 email 을 가져와 사용자 정보가 존재하는지 UserRepository 를 통해 확인합니다.
이미 존재하면 CustomOAuth2User 객체를 생성하여 반환합니다.
만약 존재하지 않으면 OAuth2SignupService.java 를 통해 회원 가입 절차를 거친 후 CustomOAuth2User 객체를 생성하여 반환합니다.

6. 인증 성공 후 처리

인증이 성공하면 OAuth2AuthenticationSuccessHandler.java 의 onAuthenticationSuccess() 가 호출 됩니다.
여기서 사용자의 정보를 바탕으로 JWT 토큰을 생성합니다.
클라이언트에게 request param 으로 필요한 정보를 전달합니다.

프론트에서는 인증 후 받은 값을 어떻게 처리할까?

이전에 인증이 성공하면 아래와 같이 필요한 값들을 Request param 에 넣어서 Redirect 하기로 되었습니다.
private String setRedirectUrl (String accessToken, UserInfoResponseDto userInfoResponseDto) { String encodedUserId = URLEncoder.encode(String.valueOf(userInfoResponseDto.id()), StandardCharsets.UTF_8); String encodedEmail = URLEncoder.encode(String.valueOf(userInfoResponseDto.email()), StandardCharsets.UTF_8); String encodedNickname = URLEncoder.encode(String.valueOf(userInfoResponseDto.nickname()), StandardCharsets.UTF_8); String encodedName = URLEncoder.encode(String.valueOf(userInfoResponseDto.name()), StandardCharsets.UTF_8); String encodedNumberTag = URLEncoder.encode(String.valueOf(userInfoResponseDto.numberTag()), StandardCharsets.UTF_8); String encodedProfiIeImageUrl = URLEncoder.encode(String.valueOf(userInfoResponseDto.profileImageUrl()), StandardCharsets.UTF_8); String encodedUserRole = URLEncoder.encode(String.valueOf(userInfoResponseDto.userRole()), StandardCharsets.UTF_8); String frontendRedirectURL = String.format( "%s/oauth/callback?token=%s&userId=%s&email=%s&nickname=%s&name=%s&numberTag=%s&profileImageUrl=%s&userRole=%s", PRE_FRONT_REDIRECT_URL, accessToken, encodedUserId, encodedEmail, encodedNickname, encodedName, encodedNumberTag, encodedProfiIeImageUrl, encodedUserRole ); return frontendRedirectURL; } }
Java
복사
저의 경우 앞의 포트가 8080 이 아닌 프론트 포트로 두었습니다. 그리고 콜백 주소에 맞추어 라우터를 설정하고, 알맞은 값을 받은 뒤 적절한 처리를 해주었습니다.

1. 콜백 라우터 설정

index.ts

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import SignupPage from "../pages/authentication/SignupPage.vue"; import LoginPage from "../pages/authentication/LoginPage.vue"; import SocialCallback from "../pages/authentication/SocialCallback.vue"; import Layout from "../layout/Layout.vue"; import PlannerList from "../pages/planner/PlannerList.vue"; import PlannerSearch from "../pages/planner/PlannerSearch.vue"; import PlannerDetail from "../pages/planner/PlannerDetail.vue"; const routes: Array<RouteRecordRaw> = [ { path: '/signup', name: 'Signup', component: SignupPage }, { path: '/login', name: 'Login', component: LoginPage }, { path: '/oauth/callback', name: 'SocialLogin', component: SocialCallback }, ... ]; const router = createRouter({ history: createWebHistory(), routes }); export default router;
TypeScript
복사

2. 콜백 페이지 생성

SocialCallback.vue

<template> </template> <script setup lang="ts"> import { onMounted } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import {useUserStore} from "../../store/userStore.ts"; const route = useRoute(); const router = useRouter(); const userStore = useUserStore(); onMounted(() => { const token = route.query.token as string; const userId = Number(route.query.userId); const nickname = route.query.nickname as string; const userTag = Number(route.query.userTag); const birthday = new Date(route.query.birthDay as string); const email = route.query.email as string; const profileImgUrl = route.query.profileImgUrl as string; const isBirthday = route.query.isBirthday === 'true'; const sex = route.query.sex as string localStorage.setItem("Authorization", token); userStore.setUserInfo({ userId, nickname, userTag, birthday, email, profileImgUrl, isBirthday, sex }); router.push('/planners'); }); </script> <style scoped lang="scss"> </style>
HTML
복사

완성 화면

아직 레이아웃을 좀 더 수정해야 하지만, 동작 과정을 보여드리기 위해 올립니다!