Files
spring-boot-examples/docs/security/07_소셜 로그인.md
2025-04-08 19:56:24 +09:00

10 KiB

아래는 "스프링 시큐리티" 책의 7장에 포함될 "OAuth2 기본 개념", "구글 등 소셜 로그인 구현", 그리고 "커스텀 OAuth2 클라이언트 설정"에 대한 내용입니다. 개념을 명확히 하고 실습 가능한 예제를 포함해 실무 적용성을 높였습니다.


7장. OAuth2와 소셜 로그인

7.1 OAuth2 기본 개념

OAuth2는 인증(Authentication)과 권한 부여(Authorization)를 위한 표준 프로토콜로, 사용자가 자신의 자격 증명을 직접 공유하지 않고도 제3자 애플리케이션이 리소스에 접근할 수 있도록 합니다. 소셜 로그인(구글, 페이스북 등)이나 API 인증에 널리 사용됩니다.

OAuth2의 주요 구성 요소
  • Resource Owner: 리소스를 소유한 사용자(예: 구글 계정 소유자).
  • Client: 리소스에 접근하려는 애플리케이션(우리의 스프링 앱).
  • Authorization Server: 사용자를 인증하고 토큰을 발급하는 서버(예: 구글 인증 서버).
  • Resource Server: 보호된 리소스를 제공하는 서버(예: 구글 API).
  • Access Token: 클라이언트가 리소스에 접근할 때 사용하는 키.
인증 흐름 (Authorization Code Grant)

가장 흔히 사용되는 흐름으로, 소셜 로그인에 적합합니다:

  1. 사용자가 클라이언트에서 "구글로 로그인" 버튼을 클릭.
  2. 클라이언트가 사용자를 Authorization Server로 리다이렉트.
  3. 사용자가 로그인 후 권한을 승인하면 Authorization Code가 클라이언트로 반환.
  4. 클라이언트가 코드를 Access Token으로 교환.
  5. Access Token으로 Resource Server에서 사용자 정보를 가져옴.

스프링 시큐리티는 OAuth2를 쉽게 통합할 수 있도록 spring-security-oauth2-client 모듈을 제공하며, 최소한의 설정으로 소셜 로그인을 구현할 수 있습니다.

7.3 구글, 깃허브 등 소셜 로그인 구현

스프링 시큐리티를 사용하면 구글, 깃허브 같은 소셜 로그인을 빠르게 구현할 수 있습니다. 여기서는 구글 로그인을 예로 설명합니다.

1. 의존성 추가

pom.xml에 다음 의존성을 추가:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2. 구글 OAuth2 클라이언트 등록
  1. Google Cloud Console에서 프로젝트 생성.
  2. "OAuth 2.0 클라이언트 ID" 생성:
    • 애플리케이션 유형: 웹 애플리케이션.
    • 리다이렉트 URI: http://localhost:8080/login/oauth2/code/google.
  3. 클라이언트 ID와 클라이언트 비밀번호(Secret)를 발급받음.
3. 설정 파일 작성

application.yml에 구글 OAuth2 설정 추가:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: your-google-client-id
            client-secret: your-google-client-secret
            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: email
4. SecurityConfig 설정
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/login").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login") // 커스텀 로그인 페이지
                .defaultSuccessUrl("/home") // 로그인 성공 시 이동
            )
            .logout(logout -> logout.logoutSuccessUrl("/"));
        return http.build();
    }
}
5. 로그인 페이지 제작

src/main/resources/templates/login.html:

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h2>로그인</h2>
    <a href="/oauth2/authorization/google">구글로 로그인</a>
</body>
</html>
  • /oauth2/authorization/google은 스프링 시큐리티가 자동 생성한 구글 로그인 경로.
6. 사용자 정보 확인

로그인 성공 후 Principal 객체로 사용자 정보를 가져올 수 있습니다:

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/home")
    public String home(@AuthenticationPrincipal OAuth2User oAuth2User, Model model) {
        model.addAttribute("name", oAuth2User.getAttribute("name"));
        model.addAttribute("email", oAuth2User.getAttribute("email"));
        return "home";
    }
}

home.html:

<!DOCTYPE html>
<html>
<body>
    <h1>환영합니다, <span th:text="${name}"></span>!</h1>
    <p>이메일: <span th:text="${email}"></span></p>
    <a href="/logout">로그아웃</a>
</body>
</html>
깃허브 로그인 추가

application.yml에 깃허브 설정 추가:

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: your-github-client-id
            client-secret: your-github-client-secret
            scope: read:user

로그인 링크: <a href="/oauth2/authorization/github">깃허브로 로그인</a>.

동작 원리
  1. 사용자가 구글 로그인 링크를 클릭하면 구글 인증 서버로 리다이렉트.
  2. 구글에서 인증 후 리다이렉트 URI로 코드를 반환.
  3. 스프링 시큐리티가 코드를 토큰으로 교환하고, 사용자 정보를 가져와 OAuth2User로 저장.
  4. 인증 성공 시 /home으로 이동.

7.4 커스텀 OAuth2 클라이언트 설정

스프링 시큐리티의 기본 설정으로 지원되지 않는 제공자(예: 네이버, 카카오)나 고급 요구사항을 처리하려면 커스텀 OAuth2 클라이언트를 설정해야 합니다.

네이버 로그인 예제
  1. 네이버 개발자 센터에서 클라이언트 ID와 Secret 발급.
  2. application.yml에 추가:
spring:
  security:
    oauth2:
      client:
        registration:
          naver:
            client-id: your-naver-client-id
            client-secret: your-naver-client-secret
            redirect-uri: "{baseUrl}/login/oauth2/code/naver"
            authorization-grant-type: authorization_code
            scope: name, email
        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 # 네이버는 사용자 정보가 'response' 객체에 포함됨
  1. 커스텀 User Service: 네이버의 사용자 정보 형식이 구글과 다르므로 OAuth2UserService를 커스터마이징:
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) {
        OAuth2User oAuth2User = super.loadUser(userRequest);
        String registrationId = userRequest.getClientRegistration().getRegistrationId();

        if ("naver".equals(registrationId)) {
            Map<String, Object> response = (Map<String, Object>) oAuth2User.getAttributes().get("response");
            return new org.springframework.security.oauth2.core.user.DefaultOAuth2User(
                oAuth2User.getAuthorities(),
                response, // 네이버의 사용자 정보
                "id" // 고유 식별자
            );
        }
        return oAuth2User;
    }
}
  1. SecurityConfig에 등록:
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomOAuth2UserService customOAuth2UserService) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
                .defaultSuccessUrl("/home")
            );
        return http.build();
    }
}
커스터마이징 포인트
  • 토큰 처리: .tokenEndpoint()로 커스텀 토큰 요청 설정.
  • 사용자 매핑: 데이터베이스에 사용자 정보를 저장하거나, 추가 속성을 매핑.
  • 에러 처리: .failureHandler()로 인증 실패 시 커스텀 로직 추가.
실습 예제
  1. 네이버 로그인 버튼 추가: <a href="/oauth2/authorization/naver">네이버로 로그인</a>.
  2. 로그인 후 반환된 사용자 정보(이름, 이메일 등)를 화면에 출력.
  3. 데이터베이스에 신규 사용자를 등록하는 로직 추가.

위 내용은 OAuth2의 기본 개념과 구글 소셜 로그인 구현, 커스텀 OAuth2 클라이언트 설정 방법을 설명했습니다. 추가적인 설정이나 예제가 필요하면 말씀해 주세요!