OAuth2 샘플 애플리케이션 구현

김준영·2023년 5월 18일
1

Code States

목록 보기
24/33

SSR 방식을 사용해서 구현

의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

HTML

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

파일 이름은 test-oauth2.html이다.

Controller

@Controller
public class HelloHomeController {

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

Config

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().authenticated())
                .oauth2Login(withDefaults());
        return http.build();
    }
}

csrf, formLogin, httpBasic을 disable 해주고 요청들은 인증된 request에 대해서만 접근을 허용하도록 하며, .oauth2Login(withDefault())를 추가해서 OAuth 2 로그인 인증을 활성화합니다.

SSR방식으로 Controller에서 test-oauth2로 요청이 들어오면 위에서 만든 html파일로 응답한다.

https://console.cloud.google.com에서 OAuth 동의와 사용자 인증을 받은 상태로 진행한다.

application.yml에 위 인증을 받고 난 후, 클라이언트 ID와 Secret을 작성한다.

위 방식으로 진행하면 애플리케이션을 실행시키고 브라우저를 통해 들어가면 구글 로그인을 통해 들어가고 html을 보여준다.

위 방식은 Spring Boot에서 자동 구성을 통한 OAuth2 인증 설정이다.

Configuration을 통해 Bean등록

@Value("${spring.security.oauth2.client.registration.google.client-id}")  // (1)
private String clientId;

@Value("${spring.security.oauth2.client.registration.google.client-secret}") // (2)
private String clientSecret;
    
@Bean
public ClientRegistrationRepository clientRegistrationRepository(){
    var clientRegistration = clientRegistration();
    return new InMemoryClientRegistrationRepository(clientRegistration);
}

private ClientRegistration clientRegistration(){
    return CommonOAuth2Provider
            .GOOGLE
            .getBuilder("google")
            .clientId(clientId)
            .clientSecret(clientSecret)
            .build();
}

ClientRegistrationRepository를 Bean으로 등록합니다.

ClientRegistrationRepository는 ClientRegistration을 저장하기 위한 Repository입니다.

⭐ Spring Boot의 자동 구성 기능을 이용할 경우, application.yml 파일에 설정된 구글의 Client ID와 Secret 정보를 기반으로 우리 눈에는 보이지 않지만 내부적으로 ClientRegistrationRepository Bean이 생성되는 반면, 지금은 우리가 Configuration을 통해 ClientRegistrationRepository Bean을 직접 등록하고 있습니다.

ClientRegistrationRepository 인터페이스의 구현 클래스인InMemoryClientRegistrationRepository의 인스턴스를 생성합니다.

클래스 이름에서도 알 수 있듯이 InMemoryClientRegistrationRepository는 ClientRegistration을 메모리에 저장합니다.

Spring Security에서는💡 CommonOAuth2Provider라는 enum을 제공하는데 CommonOAuth2Provider 는 내부적으로 Builder 패턴을 이용해 ClientRegistration 인스턴스를 제공하는 역할을 합니다.

💡 ClientRegistration은 한마디로 OAuth 2 Client에 대한 등록 정보를 표현하는 객체입니다.
우리가 구글의 API 콘솔에서 등록했던 OAuth Client ID에 대한 정보(Client ID, Secret 등)가 포함되어 있다고 생각하면 되겠습니다.

인증 확인

@Controller
public class HelloHomeController {
    @GetMapping("/hello-oauth2")
    public String home() {
        var oAuth2User = (OAuth2User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); // (1)
        System.out.println(oAuth2User.getAttributes().get("email"));   // (2)
        return "hello-oauth2";
    }
}

SecurityContextHolder에 있는 인증된 사용자 정보를 가져와 출력.

@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 "home-oauth2";
}

위와 같이 인증된 Authentication을 핸들러 메서드의 파라미터로 전달받아서 출력.

@GetMapping("/hello-oauth2")
    public String home(@AuthenticationPrincipal OAuth2User oAuth2User){
        System.out.println("User's email in Google: " + oAuth2User.getAttributes().get("email"));
        return "home-oauth2";
    }

@AuthenticationPrincipal 애너테이션을 이용해 OAuth2User 객체를 파라미터로 직접 전달받고 있습니다.

Access Token 확인

	private final OAuth2AuthorizedClientService auth2AuthorizedClientService;

    public HelloHomeController(OAuth2AuthorizedClientService auth2AuthorizedClientService) {
        this.auth2AuthorizedClientService = auth2AuthorizedClientService;
    }

	@GetMapping("/hello-oauth2")
    public String home(Authentication authentication){
        var authorizedClient = auth2AuthorizedClientService.loadAuthorizedClient("google", authentication.getName());

        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

        System.out.println("Access Token Value: " + accessToken.getTokenValue());
        System.out.println("Access Token Type: " + accessToken.getTokenType().getValue());
        System.out.println("Access Token Scopes: " + accessToken.getScopes());
        System.out.println("Access Token Issued At: " + accessToken.getIssuedAt());
        System.out.println("Access Token Expires At: " + accessToken.getExpiresAt());
        return "home-oauth2";
    }
  • OAuth2AuthorizedClientService는 권한을 부여받은 Client를 관리하는 역할을 하는데 OAuth2AuthorizedClientService를 이용해서 OAuth2AuthorizedClient 가 보유하고 있는 Access Token에 접근할 수 있기 때문에 OAuth2AuthorizedClientService를 DI 받습니다.

  • OAuth2AuthorizedClientService의 loadAuthorizedClient("google", authentication.getName())를 이용해 OAuth2AuthorizedClient 객체를 로드합니다.
    loadAuthorizedClient()를 호출하면 내부적으로 OAuth2AuthorizedClientRepository에서 OAuth2AuthorizedClient 를 조회합니다.

	@GetMapping("/hello-oauth2")
    public String home(@RegisteredOAuth2AuthorizedClient("google")OAuth2AuthorizedClient oAuth2AuthorizedClient){

        OAuth2AccessToken accessToken = oAuth2AuthorizedClient.getAccessToken();

        System.out.println("Access Token Value: " + accessToken.getTokenValue());
        System.out.println("Access Token Type: " + accessToken.getTokenType().getValue());
        System.out.println("Access Token Scopes: " + accessToken.getScopes());
        System.out.println("Access Token Issued At: " + accessToken.getIssuedAt());
        System.out.println("Access Token Expires At: " + accessToken.getExpiresAt());
        return "home-oauth2";
    }

애너테이션을 활용한 방법이다.

profile
ㅎㅎ

0개의 댓글