구글 소셜 로그인 해보기!

정지인·2025년 5월 19일

이번주 스터디 과제는 구글 소셜 로그인 해보기이다!!

소셜 로그인은 완전 처음 해보는거라 먼저 구글링부터 해보았다.

'소셜 로그인'이란 별도의 회원가입 절차 없이 소셜 미디어 계정을 이용해 간편하게 새로운 앱이나 웹 사이트를 이용할 수 있는 기능입니다.

캐치시큐

소셜 로그인은 OAuth 2.0이나 OpenID Connect 같은 인증 프로토콜을 통해 구글, 페이스북, 네이버 같은 제3자 인증 제공자(IDP, Identity Provider)에게 인증을 위임하는 방식이다.

즉, 사용자의 ID/비밀번호를 우리가 저장하거나 처리하지 않고, 외부 플랫폼이 인증을 맡게 된다.

그래서 이것의 장점은

번호장점설명
1비밀번호 관리 불필요사용자 비밀번호를 저장하거나 암호화할 필요 없음
2보안 위험 감소비밀번호 유출, 해킹 등의 위험 감소
3개발 간소화자체 로그인 시스템을 만들지 않아도 되어 개발 리소스 절감 가능
4인증 시스템 간편 구성OAuth나 OpenID Connect 연동만으로 인증 기능 구현 가능
5사용자 경험 향상새로운 계정을 만들지 않아도 되므로 진입 장벽이 낮아짐
6빠르고 편리한 로그인외부 계정으로 한 번의 클릭만으로 로그인 가능

주의할 점

인증 자체는 구글 등이 해주지만, 인증 이후 받은 사용자 정보를 어떻게 처리할지는 우리 책임이다.

즉, 로그인 후 우리 DB에 유저를 생성하거나 관리하는 로직, 세션/토큰 관리, 보안 처리 등은 여전히 개발자가 직접 신경 써야 한다!


1단계: 기존 로그인 → JWT 방식으로 리팩토링

나는 RequestParam 형식으로 작성해서 보안이 취약하기 때문에
기존의 형식에서 JWT 토큰 형식으로 바꿔주기로 결정했다 !!!

  • 이메일 + 비밀번호 로그인 검증
  • 로그인 성공 시 JWT 발급
  • 이후 요청에서 JWT로 인증 처리
  • 클라이언트가 ?email=xxx 파라미터 넘기는 방식은 제거

2단계: 구글 소셜 로그인 연동

  • Spring Security + OAuth2 설정 추가
  • 로그인 성공 시 JWT 토큰 생성
  • 토큰을 HTTP 응답 헤더 또는 리다이렉트 URL로 전달

현재 확인된 사항 요약

항목내용
로그인 방식이메일 + 비밀번호, POST /user/login
인증 방식세션/쿠키 없음, 파라미터로 email 넘김
Spring Security❌ 없음 → 앞으로 도입해야 함

라는 문제점들이 있었다 !!! 그래서 먼저 이 부분부터 해결한 후에 소셜 로그인을 도전해야겠다고 생각했다.


1단계: 기존 로그인 → JWT 방식으로 리팩토링

을 만들어 주기 위해서 일단 기존에 RequestParam을 통해 email을 받아주던 코드들을 모두 지워주고 , 로그인 시 JWT 토큰을 HttpOnly 쿠키에 저장하는 형식으로 바꿔주었다 !!

그 이후 인증 요청 시 쿠키에서 토큰을 읽어 인증 처리를 하게 했고 로그아웃 시 쿠키를 삭제하는 형식으로 만들었다.

이번주 주제가 토큰은 아니니 자세한 설명은 건너 뛰었다.


2단계: 구글 소셜 로그인 연동

1. Google OAuth2 클라이언트 등록

1-1. 프로젝트 생성

  • Google Cloud Console 접속 → 프로젝트 생성

1-2. OAuth 동의 화면 구성

  • 외부(External) 사용자 선택

  • 앱 이름, 이메일, 도메인 등록

  • 범위: email, profile 체크

1-3. OAuth2 Client ID 생성

  • 애플리케이션 유형: 웹 애플리케이션

  • 승인된 리디렉션 URI:

http://localhost:8080/login/oauth2/code/google

1-4. 클라이언트 ID / 시크릿 복사

  • .yml 또는 .properties 파일에 추가

출처-구글 소셜 로그인 설정


2. application.yml 세팅

  security:
    oauth2:
      client:
        registration:
          google:
            client-id: [발급받은 client-id]
            client-secret: [발급받은 client-secret]
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope:
              - profile
              - email
        provider:
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://oauth2.googleapis.com/token
            user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
            user-name-attribute: sub
  jwt:
    secret: [너의 JWT 비밀 키]
FRONT_ADDRESS: http://localhost:3000
LOGIN_SUCCESS_REDIRECT_PAGE: /info
LOGIN_SUCCESS_REDIRECT_PAGE_IF_INFO_ALREADY_EXISTS: /main

3. 메서드 구현

메서드를 구현하기 전에 각 각의 메서드가 어떤 역할을 하고 왜 필요한 지에 대해서 찾아보았다.

메서드를 아직 만들진 않았지만, 동아리 회장님이 올려준 코드들을 참고하여 어떤 메서드가 필요한 지 판단했고, 그 메서드에 대해서 조사해봤다.

#SecurityConfig

- Spring Security 전반 설정을 담당
- URL 별 접근 권한 설정 (`.authorizeHttpRequests`)
- OAuth2 로그인 관련 설정 (`.oauth2Login`)
- JWT 필터 추가 (`.addFilterBefore`)
- CSRF, 기본 로그인폼 비활성화

먼저 이 부분은 Jwt 토큰을 활용하면서 미리 만들어놨던 메서드라 일부 수정만 해주면 됐어서 정말 편했다.

#JwtUtil (JwtTokenProvider)

- JWT 토큰 생성 및 검증 전담 클래스
- 주요 메서드:
  - `createToken(email, role)` : 사용자 정보를 기반으로 JWT 생성
  - `getEmail(token)` : 토큰에서 이메일 추출
  - `getRole(token)` : 토큰에서 사용자 역할 추출
  - `isTokenExpired(token)` : 토큰 만료 여부 확인
- 서명 키(`secretKey`)를 통해 토큰 서명/검증

이 부분이 기존에 있던 JwtTokenProvider 메서드와 닮았어서 기존에 있던 메서드를 리펙토링 해줘야하나 , 아니면 아에 별도로 새로 만들어야하나 엄청 헷갈렸다.

결론은 똑같이 사용자 정보를 기반으로 토큰을 만들어주는 메서드라 기존의 메서드를 활용해서 만들었다 !

#CustomOAuth2UserService

- Google 로그인 성공 시 사용자 정보를 받아오는 서비스
- Google OAuth2에서 제공하는 `OAuth2UserRequest`를 받아 사용자 정보 추출
- DB에 해당 유저가 없을 경우 새로 저장
- 반환된 `OAuth2User` 객체는 SecurityContext에 저장되어 인증에 사용됨

이 메서드가 구글에서 사용자 정보를 받아오는 메서드이다 !

여기서 오류가 굉장히 많이 나서 답답해 죽는 줄 알았는데 알고 보니까 또 의존성 까먹었다

#OAuth2SuccessHandler

- OAuth2 로그인 성공 직후 실행되는 핸들러
- 사용자 정보 (`email`, `role`) 를 추출
- `JwtUtil`을 이용해 JWT 토큰 발급
- 토큰을 응답 헤더 또는 쿠키에 담아 클라이언트로 전달
- 필요에 따라 프론트엔드로 리다이렉션 처리

이 친구도 같이 더블로 오류가 나서 왜 안되나 했는데 .... 의존성을 넣어주니까 다행히 잘 됐다!!

#JwtAuthenticationFilter

- 모든 요청에 대해 JWT 유효성 검사 수행
- `Authorization` 헤더에서 토큰 추출
- `JwtUtil`로 검증 후, 유효하면 인증 객체 생성하여 SecurityContext에 저장
- 이후 컨트롤러에서 `@AuthenticationPrincipal` 또는 `SecurityContextHolder`로 사용자 정보 접근 가능

이 메서드도 jwt를 활용하면서 만들었던 메서드를 재활용할 수 있어서 좋았다!!

아무튼 이러한 메서드들이 존재했고 나는 이 메서드들이 서로 어떻게 상호작용하는지 전체적인 흐름이 궁굼했다!!

근데 자세히 알려주는 곳이 없어서
GPT를 통해서 전체적인 흐름을 물어봤다.

1️⃣ 사용자가 /oauth2/authorization/google 로 접속
    ↓ (Spring Security 내장 처리)
2️⃣ 로그인 성공 시 → OAuth2SuccessHandler.onAuthenticationSuccess()
    ↓
3️⃣ 사용자 정보(email, role) 추출
    ↓
4️⃣ JwtTokenProvider.createToken(email, role) → JWT 발급
    ↓
5️⃣ JWT를 "token"이라는 이름의 쿠키로 생성 → 응답에 추가
    ↓
6️⃣ 클라이언트는 이후 모든 요청에서 이 쿠키를 자동 포함
    ↓
7️⃣ JwtAuthenticationFilter에서 요청마다 token 쿠키 확인
    ↓
8️⃣ JwtTokenProvider.validateToken() → 유효성 검사
    ↓
9️⃣ SecurityContext에 인증 객체 설정 → 로그인된 상태 유지

이것을 또 간략하게 설명해줬는데 ...

  1. [로그인 요청] → /oauth2/authorization/google
  2. [로그인 성공] → OAuth2SuccessHandler → JWT 발급
  3. [응답 전송] → token 쿠키 포함 + 리다이렉트
  4. [이후 요청들] → JwtAuthenticationFilter → 인증 유지

이런식으로 흘러간다는 것을 알았다!!


결과 ....

안댐

왜 안될까??

먼저 디버깅 오류를 분석해봤더니

아 !! 내가 test할 url을 넣지 않았구나 하며 급하게 저 코드를 넣어서 다시 돌려봤다!

?

하하 근데 사실 저 url은 필요 없었다 !!

에초에 나는 프론트 서버를 돌리고 있지 않는데 어떻게 프론트 서버에서 테스트를 해보겠나 .....

바로 url을

board/list로 바로 게시글 목록으로 넘어가게 바꿔주었다 !

그러니 잘 넘어갔다 !!

유저 메인 페이지에서도 로그인이 유지되어있는 걸 확인할 수 있었다!


산 넘어 산

이젠 또 다른 오류가 날 괴롭혔다 !!

이젠 로그아웃을 하고 index 페이지로 넘어가는데 .... 어라라 왜 로그인 페이지가 없지 ??

내가 관리자 계정을 생성하면서 인덱스 페이지에 로그인이 되어있으면 로그인이 안 뜨게 html을 설정해놨었다.

@GetMapping("/user/logout")
    public String logout(HttpServletResponse response) {
        
        Cookie cookie = new Cookie("token", null);
        cookie.setHttpOnly(true);
        cookie.setPath("/");
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        return "redirect:/user/login";
    }

하지만 내가 작성한 코드를 보면 로그아웃을 하면 쿠키를 즉시 만료 시키고 다시 로그인 페이지로 리다이렉트 해줬지만, 어떤 이유에서 인지 토큰은 지워졌는데 계속 로그인이 되어있는 상태였다.

원인을 찾아보니 Security에서 계속 인증 완료인 상태라 로그아웃을 해서 토큰을 없애도 계속 로그인 상태가 유지되고 있었다 !!

그래서

SecurityContextHolder.clearContext();

이 코드를 추가 해주어 Security 인증을 초기화 시켜주어 제대로 로그아웃을 시켜주었다 !!

이젠 그럴싸 하게 잘 돌아간다 ...


느낀 점 !!!

첫 번째 주는 사실 아이디어톤 준비 때문에 기존 RequestParam 형식에서 Jwt 토큰 형식으로 바꾸는 것 까지 밖에 제대로 하지 못했다. 그래도 동아리 회장님이 올려주신 코드들을 보면서 대충 어떤 구조로 구현해야하고 어떤 메서드를 만들어야 하는지 알 수 있어서 좋았다!!

그리고 JWT 토큰으로 바꾸면서 계속 전에 스터디했을 때 희원님이 설명해주신 기억이 계속 나면서 그래도 어떤 방식으로 JWT가 흘러가는 지는 이해가 잘 됐다. 이게 스터디의 기능일까??

전에 해놨던 방식이 html에서도 직접적으로 email을 받고 있던 부분이 많아서 거의 모든 코드들을 고쳐줘야했어서 엄청 복잡하고 힘들었다. 그래도 모르는 부분은 GPT나 우리 스터디분들 Velog를 참고하면서 잘 리팩토링 해나갔다.

소셜 로그인을 처음 들어봐서 사실 엄청 걱정했다 ... 하지만 jwt 토큰을 이용하면 정말 금방 할 수 있었다 !! 하지만 나의 jwt 토큰 생성기나, Security 코드가 완전하지 않아서 많은 오류들과 싸웠지만 정말 잘 구현되어있는 코드였다면 잘 넘어갈 수 있을 것 같았다!

그래도 완성된 결과를 보니 정말 뿌듯하고 멋있었다... 별거 아니지만 내가 이런거 까지 할 수 있다니 하면서 혼자 뿌듯해했다 ...

같은 동아리 분 중에 카카오톡으로도 구글과 똑같이 API 생성해서 소셜 로그인을 할 수 있다고 들어서 다음에는 카카오톡으로도 로그인할 수 있게 만들어보고 싶다!!

그리고 아직은 local에서만 돌릴 수 있어서 다음엔 배포까지 할 수 있으면 좋겠다!!

profile
멋쟁이사자 13기 백엔드

2개의 댓글

comment-user-thumbnail
2025년 5월 20일

너무너무 고생하셨어요ㅜ

답글 달기
comment-user-thumbnail
2025년 5월 20일

저도 페이지 넘어가는 부분에서 오류가 너무 많이 나서 미칠 지경입니다。。 같이 파이팅 해요

답글 달기