WY J
학습 공간
WY J
  • 분류 전체보기 (95)
    • Java (38)
      • 알고리즘 (5)
      • 자료구조 (4)
      • 기초 (9)
      • OOP (10)
      • Collection (3)
      • Effective (5)
      • reator (2)
    • HTML&CSS (5)
    • macOS (3)
    • Git (5)
    • Network (5)
    • MySQL (2)
    • Spring Boot (31)
      • Core (5)
      • MVC (15)
      • Security (10)
    • 알고리즘 (1)
    • Cloud (3)
      • AWS (3)
    • Docker (1)
    • Project (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

최근 글

hELLO · Designed By 정상우.
WY J

학습 공간

[Spring Security] OAuth2 인증(Authentication) 구현
Spring Boot/Security

[Spring Security] OAuth2 인증(Authentication) 구현

2022. 9. 29. 17:33

OAuth2 로그인 인증을 처리하기 위한 OAuth2 인증 프로토콜을 제공하는 Google의 서비스를 이용하여 구현해본다.

 

구글 OAuth2 인증 사전 작업

 

1. 구글 API 및 서비스 콘솔로 접속한다. https://console.cloud.google.com/apis

 

2. 새 프로젝트를 생성한다.

 

3. OAuth 동의 화면 탭에서 User Type을 외부로 지정하고 만들기를 클릭한다.

 

4. 앱 이름, 사용자 지원 이메일, 이메일 주소 칸을 입력한 후 저장 후 계속을 클릭한다.

 

5. 사용자 인증 정보 탭에서 사용자 인증 정보 만들기 - OAuth 클라이언트 ID를 클릭한다.

 

6. 애플리케이션 유형 - 웹 애플리케이션 선택, 이름, 승인된 리디렉션 URI (http://localhost:8080/login/oauth2/code/google)를 입력한다.

 

7. OAuth 클라이언트 생성됨 창에서 클라이언트 ID와 보안 비밀번호를 확인한다.

클라이언트 ID와 클라이언트 보안 비밀번호(Secret)은 Spring Security 기반의 애플리케이션의 설정 정보로 사용되므로 안전하게 보관해둔다.


OAuth2 Sample Application 구현

서버 측에서 HTML 렌더링하는 SSR 방식으로 구현해보도록 한다.

 

1. build.gradle 의존 라이브러리 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // HTML 화면 구성을 위한 템플릿 타임리프(Thymeleaf
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security 기반
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // OAuth2 클라이언트 역할을 위해 추가
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'org.mapstruct:mapstruct:1.5.2.Final'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final'
    implementation 'org.springframework.boot:spring-boot-starter-mail'
    implementation 'com.google.code.gson:gson'
}

 

2. HTML 파일 생성

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Welcome to Hello OAuth 2.0</title>
</head>
<body>
    <div style="text-align: center"><h2>Welcome to Hello OAuth 2.0!!</h2></div>
</body>
</html>

 

3. HomeController 작성

HTML 화면에 대한 뷰를 리턴하는 컨트롤러이다. SSR 방식의 핸들러 메서드의 리턴 타입이 String이면 뷰 이름(hello-oauth2)을 리턴하며, 최종적으로 hello-oauth2.html을 웹브라우저로 전송한다.

@Controller
public class HelloHomeController {
    @GetMapping("/hello-oauth2")
    public String home() {
        return "hello-oauth2";
    }
}

 

4. OAuth2 클라이언트 등록 정보 추가

spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  sql:
    init:
      data-locations: classpath*:db/h2/data.sql
  security:
    oauth2:
      client:
        registration:
          google:
            clientId: xxxx
            clientSecret: xxxx

clientId와 clientSecret은 보안성을 생각하여 환경변수로 등록해서 사용해야 한다.

 

5. OAuth2 인증을 위한 SecurityConfiguration 설정

import org.springframework.beans.factory.annotation.Value;
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.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
public class SecurityConfiguration {
    @Value("${spring.security.oauth2.client.registration.google.clientId}") // application.yml에 설정한 ID를 로드
    private String clientId;

    @Value("${spring.security.oauth2.client.registration.google.clientSecret}") // application.yml에 설정한 Secret을 로드
    private String clientSecret;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .authorizeHttpRequests(authorize -> authorize // 인증된 request에 대해서만 접근을 허용
                        .anyRequest().authenticated()
                )
                .oauth2Login(withDefaults()); // OAuth2 로그인 인증을 활성화

        return http.build();
    }

    /** ClientRegistration을 저장하기 위한 Repository */
    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        var clientRegistration = clientRegistration(); // clientRegistration 인스턴스를 리턴 받는다.

        // ClientRegistrationRepository 인터페이스의 구현 클래스인 InMemoryClientRegistrationRepository의 인스턴스를 생성
        return new InMemoryClientRegistrationRepository(clientRegistration);
    }

    /** ClientRegistration 인스턴스를 생성하는 메서드 */
    private ClientRegistration clientRegistration() {

        // Spring Security에서 제공하는 enum 타입의 CommonOAuth2Provider는 내부적으로 Builder 패턴을 이용해 ClientRegistration 인스턴스를 제공한다.
        return CommonOAuth2Provider
                .GOOGLE
                .getBuilder("google")
                .clientId(clientId)
                .clientSecret(clientSecret)
                .build();
    }
}

 

6. 인증된 Authentication 정보 확인

구글의 OAuth2 인증이 성공적으로 수행되어 SecurityContext에 인증된 Authentication이 저장되었는지 확인해본다.

 

6-1. SecurityContext를 이용한 방법

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloHomeController {

    @GetMapping("/hello-oauth2")
    public String home() {

        // SecurityContext에서 인증된 Authentication 객체를 통해 Principal 객체를 얻는다.
        // OAuth2로 로그인 인증을 수행 했으므로 SecurityContext에 저장된 Principal은 OAuth2User 객체로 캐스팅할 수 있다.
        var oAuth2User = (OAuth2User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        // OAuth2User 객체에 저장되어 있는 사용자 정보를 getAttributes() 메서드로 얻을 수 있다.
        System.out.println(oAuth2User.getAttributes().get("email"));

        return "hello-oauth2";
    }
}

SecurityContext에서 인증된 Authentication을 얻은 후, 사용자의 이메일을 출력했을 때 정상적으로 출력된다면 OAuth2 인증에 성공한 것이다.

 

 

6-2. Authentication 객체를 핸들러 메서드 파라미터로 전달 받는 방법

@Controller
public class HelloHomeController {

    // 인증된 Authentication을 핸들러 메서드의 파라미터로 받는다.
    @GetMapping("/hello-oauth2")
    public String home(Authentication authentication) { 

        var oAuth2User = (OAuth2User)authentication.getPrincipal();
        System.out.println(oAuth2User);
        System.out.println("User's email in Google: " + oAuth2User.getAttributes().get("email"));

        return "hello-oauth2";
    }
}

 

6-3. OAuth2User를 파라미터로 전달 받는 방법

@Controller
public class HelloHomeController {

    // @AuthenticationPrincipal을 이용해 OAuth2User 객체를 파라미터로 직접 받는다.
    @GetMapping("/hello-oauth2")
    public String home(@AuthenticationPrincipal OAuth2User oAuth2User) {

        System.out.println("User's email in Google: " + oAuth2User.getAttributes().get("email"));

        return "hello-oauth2";
    }
}

 

7. Authorization Server로 부터 전달 받은 Access Token 확인

구글의 OAuth2 인증이 성공적으로 수행되면 내부적으로 리소스 서버에 접근할 때 사용되는 Access Token을 전달받게 된다.

OAuth2 인증 후, 전달 받은 Access Token의 정보를 확인해 본다.

 

7-1. OAuth2AuthorizedClientService를 DI 받는 방법

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloHomeController {
    private final OAuth2AuthorizedClientService authorizedClientService;

    // 권한을 부여받아 Access Token을 보유하고 있는 OAuth2AuthorizedClient(클라이언트)에 접근하기 위해
    // 이를 관리해주는 OAuth2AuthorizedClientService를 DI 받는다.
    public HelloHomeController(OAuth2AuthorizedClientService authorizedClientService) {
        this.authorizedClientService = authorizedClientService;
    }

    @GetMapping("/hello-oauth2")
    public String home(Authentication authentication) {

        // authorizedClientService의 loadAuthorizedClient() 메서드를 이용해 OAuth2AuthorizedClientRepository에서 OAuth2AuthorizedClient 객체를 로드한다.
        var authorizedClient = authorizedClientService.loadAuthorizedClient("google", authentication.getName());

        // authorizedClient의 getAccessToken() 메서드로 OAuth2AccessToken 객체를 얻는다.
        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
        System.out.println("Access Token Value: " + accessToken.getTokenValue()); // AccessToken 문자열 출력
        System.out.println("Access Token Type: " + accessToken.getTokenType().getValue()); // Token Type 출력
        System.out.println("Access Token Scope: " + accessToken.getScopes()); // Token 리소스 접근 범위 목록 출력
        System.out.println("Access Token Issued At: " + accessToken.getIssuedAt()); // Token 발행일시 출력
        System.out.println("Access Token Expires At: " + accessToken.getExpiresAt()); // Token 만료일시 출력

        return "hello-oauth2";
    }
}

 

 

7-2. OAuth2AuthorizedClient를 핸들러 메서드의 파라미터로 전달 받는 방법

@Controller
public class HelloHomeController {

    // @RegisteredOAuth2AuthorizedClient를 이용해 OAuth2AuthorizedClientRepository에 있는 OAuth2AuthorizedClient를 파라미터로 전달 받는다.
    @GetMapping("/hello-oauth2")
    public String home(@RegisteredOAuth2AuthorizedClient("google")OAuth2AuthorizedClient authorizedClient) {

        // authorizedClient의 getAccessToken() 메서드로 OAuth2AccessToken 객체를 얻는다.
        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
        System.out.println("Access Token Value: " + accessToken.getTokenValue()); // AccessToken 문자열 출력
        System.out.println("Access Token Type: " + accessToken.getTokenType().getValue()); // Token Type 출력
        System.out.println("Access Token Scope: " + accessToken.getScopes()); // Token 리소스 접근 범위 목록 출력
        System.out.println("Access Token Issued At: " + accessToken.getIssuedAt()); // Token 발행일시 출력
        System.out.println("Access Token Expires At: " + accessToken.getExpiresAt()); // Token 만료일시 출력

        return "hello-oauth2";
    }
}

 

'Spring Boot > Security' 카테고리의 다른 글

[Spring Security] OAuth2 + JWT 인증 구현  (0) 2022.09.30
[Spring Security] OAuth2란?  (1) 2022.09.29
[Spring Security] JWT 로그인 인증, 자격 증명 구현  (0) 2022.09.28
[Spring Security] JWT 인증(JSON Web Token Authentication)  (0) 2022.09.26
[Spring Security] 권한 부여(Authorization) 구성 요소  (0) 2022.09.26
    'Spring Boot/Security' 카테고리의 다른 글
    • [Spring Security] OAuth2 + JWT 인증 구현
    • [Spring Security] OAuth2란?
    • [Spring Security] JWT 로그인 인증, 자격 증명 구현
    • [Spring Security] JWT 인증(JSON Web Token Authentication)
    WY J
    WY J

    티스토리툴바