아래는 "스프링 시큐리티" 책의 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`에 다음 의존성을 추가:
```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](https://console.cloud.google.com)에서 프로젝트 생성.
2. "OAuth 2.0 클라이언트 ID" 생성:
   - 애플리케이션 유형: 웹 애플리케이션.
   - 리다이렉트 URI: `http://localhost:8080/login/oauth2/code/google`.
3. 클라이언트 ID와 클라이언트 비밀번호(Secret)를 발급받음.

##### 3. 설정 파일 작성
`application.yml`에 구글 OAuth2 설정 추가:
```yaml
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 설정
```java
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`:
```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` 객체로 사용자 정보를 가져올 수 있습니다:
```java
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`:
```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`에 깃허브 설정 추가:
```yaml
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`에 추가:
```yaml
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' 객체에 포함됨
```

3. **커스텀 User Service**:
네이버의 사용자 정보 형식이 구글과 다르므로 `OAuth2UserService`를 커스터마이징:
```java
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;
    }
}
```
4. **SecurityConfig에 등록**:
```java
@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 클라이언트 설정 방법을 설명했습니다. 추가적인 설정이나 예제가 필요하면 말씀해 주세요!