본문 바로가기
[개발] Info/용어

OAuth

by Devsong26 2023. 11. 17.

OAuth는 인터넷 사용자가 비밀번호를 제공하지 않고도, 다른 웹사이트의 리소스에 접근할 수 있도록 허용하는 개방형 표준입니다. OAuth를 통해 사용자는 안전하게 특정 권한을 가진 서비스나 애플리케이션에 제한된 자원 접근을 승인할 수 있습니다.

 

OAuth는 일반적으로 두 가지 버전, 즉 OAuth 1.0과 OAuth 2.0으로 나뉩니다.

 

OAuth 1.0

OAuth 1.0은 2010년에 발표된 최초의 OAuth 표준입니다. 

 

이 표준의 주요 특징은 다음과 같습니다:

  • 서명된 요청
    • OAuth 1.0은 클라이언트가 서버에 요청을 보낼 때마다 요청을 서명하는 방식을 사용합니다. 이를 위해 클라이언트는 소비자 키(consumer key)와 소비자 비밀(consumer secret)을 사용합니다.
  • 보안
    • 서명된 요청은 요청이 변경되지 않았음을 보증하고, 중간자 공격을 방지합니다. 이는 HTTPS가 없는 환경에서도 보안을 제공합니다.
  • 복잡한 구현
    • 서명 과정은 구현을 복잡하게 만들며, 개발자들이 OAuth 1.0을 구현하는 데 어려움을 겪게 합니다.

 

OAuth 2.0

OAuth 2.0은 OAuth 1.0의 복잡성을 해결하고 확장성을 개선하기 위해 2012년에 발표된 새로운 버전입니다. 

 

OAuth 2.0의 주요 특징은 다음과 같습니다:

  • 보다 간단한 프로세스
    • OAuth 2.0은 클라이언트 인증과 토큰 발급 프로세스를 간소화했습니다. 이는 구현을 용이하게 하고, 개발자들의 부담을 줄여줍니다.
  • 다양한 인증 흐름
    •  OAuth 2.0은 다양한 인증 흐름(예: Authorization Code, Implicit, Password Credentials, Client Credentials)을 제공하여 다양한 시나리오와 애플리케이션 유형에 맞게 적용할 수 있습니다.
  • 보안 고려사항
    • OAuth 2.0은 HTTPS를 통한 보안 통신을 권장합니다. OAuth 1.0에서 제공하는 요청 서명 메커니즘은 제거되었으며, 대신 토큰을 안전하게 전송하고 관리하는 것에 중점을 둡니다.
  • 확장성
    • OAuth 2.0은 다양한 유형의 애플리케이션을 지원하도록 설계되었으며, 새로운 흐름이나 토큰 유형을 쉽게 추가할 수 있습니다.

 


 

글로벌하게 사용되며 검색 결과가 많이 나오는 구글을 OAuth의 실습 플랫폼으로 정하였습니다.

 

스펙은 아래와 같습니다.

  • spring boot: 3.1.5
  • java: 17

 

실습을 진행해 보겠습니다.

 

Gradle에 의존성 추가하기

dependencies {
    // ...

    // Spring Security
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // OAuth2 Client
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    
    // 타임리프
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

    // ...
}

 

 

Google Cloud Platform 셋팅하는 방법

더보기

Google Cloud Platform(GCP)에서 OAuth 2.0 클라이언트 ID를 생성하는 방법은 다음과 같습니다:

  • Google Cloud Platform 콘솔 방문
  • 프로젝트 생성 또는 선택
    • 새 프로젝트를 만들거나 기존 프로젝트를 선택합니다. 새 프로젝트를 만들기 위해서는 '프로젝트 만들기' 버튼을 클릭하고 필요한 정보를 입력합니다.
  • API 및 서비스 대시보드로 이동
    • GCP 콘솔의 사이드바에서 'API 및 서비스' > '대시보드'로 이동합니다.
  • OAuth 동의 화면 구성
    • 'OAuth 동의 화면'을 선택하고, 필요한 정보를 입력합니다. 이 단계에서는 애플리케이션 이름, 사용자 지원 이메일, 동의 화면에 표시될 정보 등을 설정합니다. '저장' 버튼을 클릭하여 변경 사항을 저장합니다.
  • API 사용 설정
    • 필요한 API(예: Google+ API, Gmail API 등)를 활성화합니다. 'API 및 서비스' > '라이브러리'에서 필요한 API를 검색하고 '사용' 버튼을 클릭합니다.
  • OAuth 2.0 클라이언트 ID 생성
    • 'API 및 서비스' > '사용자 인증 정보'로 이동한 후, '사용자 인증 정보 만들기' 버튼을 클릭하고 'OAuth 클라이언트 ID'를 선택합니다.
  • 애플리케이션 유형 선택
    • 애플리케이션의 유형을 선택합니다(예: 웹 애플리케이션, iOS, Android 등).
  • 리디렉션 URI 설정
    • 애플리케이션의 로그인 후 사용자를 리디렉션할 URI를 입력합니다. 이 URI는 애플리케이션의 설정 파일에도 동일하게 명시되어야 합니다.
  • 클라이언트 ID 및 비밀 생성
    • 폼을 완성하고 '생성' 버튼을 클릭하면 클라이언트 ID와 클라이언트 비밀이 생성됩니다. 이 정보를 안전하게 보관하고 애플리케이션의 설정 파일에 사용합니다.
  • 사용자 인증 정보 다운로드
    • 생성된 사용자 인증 정보를 JSON 파일로 다운로드하여 프로젝트에 안전하게 저장할 수 있습니다.


이 과정을 통해 Google Cloud Platform에서 OAuth 2.0 클라이언트 ID를 생성하고, 이를 애플리케이션에 사용하여 Google 서비스에 대한 OAuth 인증을 구현할 수 있습니다.

 

애플리케이션 설정하기

 

yml 파일에 아래 설정을 추가합니다.

 

# Google OAuth 설정
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: #{CLIENT_ID}
            client-secret: #{CLIENT_SECRET}
            scope: profile,email
            redirect-uri: http://localhost:8080/login/oauth2/code/google
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
  main:
    allow-bean-definition-overriding: true

 

프론트는 타임리프로 구현했습니다.

redirect-uri는 아래와 같은 특징을 가집니다.

redirect-uri는 일반적으로 OAuth 2.0 공급자가 사용자를 리디렉션하는 콜백 URL입니다.
이 URL은 인증 공급자로부터 인증 코드를 받는 데 사용됩니다. Spring Security는 이 코드를 받고, 그것을 사용해서 사용자를 인증한 후, CustomAuthenticationSuccessHandler를 호출합니다.

따라서, redirect-uri 경로에 대한 명시적인 컨트롤러 메서드가 없더라도, Spring Security가 OAuth 2.0 로그인 프로세스를 처리하고, 로그인 성공 시 CustomAuthenticationSuccessHandler를 통해 추가적인 처리를 수행하게 됩니다.

 

 

SecurityConfiguration 설정하기

 

@Configuration
@EnableReactiveMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
            .authorizeExchange((auths) ->
                auths
                    .pathMatchers("/", "/login/oauth2/code/google").permitAll() // 공개 경로 설정
                    .anyExchange().authenticated()
            )
//            .formLogin((formLogin) ->
//                    formLogin.loginPage("/login")
//            )
            .logout((logout) ->
                logout.logoutUrl("/logout") // 로그아웃을 처리할 URL
                      .logoutSuccessHandler(new CustomLogoutSuccessHandler())
            )
            .oauth2Login(oauth2Login ->
                    oauth2Login.authenticationSuccessHandler(new CustomAuthenticationSuccessHandler())
            )
            .build()
        ;
    }

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository(
            ClientRegistration googleClientRegistration){
        return new InMemoryClientRegistrationRepository(googleClientRegistration);
    }

    @Bean("googleClientRegistration")
    ClientRegistration googleClientRegistration(
            @Value("${spring.security.oauth2.client.registration.google.client-id}") String clientId,
            @Value("${spring.security.oauth2.client.registration.google.client-secret}") String clientSecret,
            @Value("${spring.security.oauth2.client.registration.google.scope}") String scope,
            @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") String redirectUri) {

        return ClientRegistration.withRegistrationId("google")
                .clientId(clientId)
                .clientSecret(clientSecret)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri(redirectUri)
                .scope(List.of(scope.split(",")))
                .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
                .tokenUri("https://www.googleapis.com/oauth2/v4/token")
                .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
                .userNameAttributeName(IdTokenClaimNames.SUB)
                .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
                .clientName("Google")
                .build();
    }

}


public class CustomAuthenticationSuccessHandler extends RedirectServerAuthenticationSuccessHandler {

    public CustomAuthenticationSuccessHandler() {
        super("/main"); // 성공 시 리디렉션할 URL 지정
    }

    @Override
    public Mono<Void> onAuthenticationSuccess(WebFilterExchange exchange, Authentication authentication) {


        return super.onAuthenticationSuccess(exchange, authentication);
    }
}


public class CustomLogoutSuccessHandler implements ServerLogoutSuccessHandler {

    @Override
    public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
        URI uri = URI.create("/");

        // 쿠키 삭제
        ResponseCookie cookie = ResponseCookie.from("JSESSIONID", "")
                .path("/") // 쿠키의 경로 설정 (필요에 따라 변경)
                .maxAge(0) // 쿠키 만료 시간을 0으로 설정하여 쿠키 삭제
                .build();
        exchange.getExchange().getResponse().addCookie(cookie);

        // 리디렉션 설정
        exchange.getExchange().getResponse().setStatusCode(HttpStatus.SEE_OTHER);
        exchange.getExchange().getResponse().getHeaders().setLocation(uri);

        return exchange.getExchange().getResponse().setComplete();
    }

}

 

redirect-uri는 모두 허용 설정을 해야 합니다.

 

 

 

 

컨트롤러

 

WebFlux를 사용한 예시입니다.

@Controller
public class OAuthViewController {

    @GetMapping("/")
    public Mono<String> index(){
        return Mono.just("index");
    }

    @GetMapping("/main")
    public Mono<Rendering> renderView() {
        return ReactiveSecurityContextHolder.getContext()
                .map(securityContext -> (OAuth2AuthenticationToken) securityContext.getAuthentication())
                .map(token -> {
                    final Map<String, Object> attr = token.getPrincipal().getAttributes();

                    return Rendering.view("main")
                            .modelAttribute("name", attr.get(OAuthAttr.name.name()))
                            .modelAttribute("email", attr.get(OAuthAttr.email.name()))
                            .build();
                });
    }

}

 

 

View page

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>OAuth 2.0 로그인</title>
</head>
<body>
<a href="/oauth2/authorization/google">Google Login</a><br>

</body>
</html>

 

main.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Main Page</title>
</head>
<body>
로그인 사용자 정보 <br>
이름 >> <span th:text="${name}"></span> <br>
email >> <span th:text="${email}"></span>

<br>
<br>
<a href="/logout">logout</a><br>
</body>
</html>

 

 

실습 순서는 아래와 같습니다.

  • 스프링 앱을 기동합니다.
  • localhost:8080/ 로 접속합니다.
  • 화면의 구글 로그인을 클릭하여 OAuth 로그인을 합니다.
  • localhost:8080/main 으로 접속하여 화면에 표시된 email, name이 올바른지 확인합니다.
  • 화면에 로그아웃을 클릭하여 로그아웃합니다.

 


 

 

참고

- https://velog.io/@chrkb1569/OAuth-2.0%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84

'[개발] Info > 용어' 카테고리의 다른 글

CQRS(Command Query Responsibility Segregation)  (0) 2023.11.30
CDC(Change Data Capture)  (0) 2023.11.29
SSL  (0) 2023.11.17
OSI 7 Layer  (0) 2023.10.24
gRPC  (0) 2023.10.22