[OAuth 2.0] Spring Security 없이 구현 중, "invalid_scope" 해결

Eunbi Lee·2024년 4월 5일

MDP

목록 보기
2/6
post-thumbnail

1. Situation

Spring Security 없이 Google OAuth2를 구현하던 중, invalid_scope란 오류를 마주했다.

사건의 발단은 다음과 같았다.

  1. Google Cloud Platform에서 OAuth 동의 화면 및 사용자 인증 정보 설정을 마친다.
  2. 이때, redirection_url = https://localhost:8080/login/oauth2/code/google으로 설정
  3. application-oauth.yaml파일을 다음과 같이 설정
oauth2:
  google:
    client-id: CLIENT_ID
    client-secret: CLIENT_PASSWORD
    redirect-uri: https://localhost:8080/login/oauth2/code/google
    token-uri: https://oauth2.googleapis.com/token
    resource-uri: https://www.googleapis.com/oauth2/v2/userinfo
  1. Access Token 발급 로직을 구현
  2. 하단의 링크에서 CLIENT_IDREDIRECT_URI를 발급받은 클라이언트 ID 및 설정한 redirection_url로 변경
  1. 링크를 통해, 구글 로그인 페이지가 화면에 노출
  2. 액세스 거부 오류 발생
  3. 또는 계정 선택 후, 계속 진행 시 redirect_uri_mismatch 오류 발생

2. Task

목표

정상적으로 Google 로그인을 완료하여 Access Token을 얻어내는 것

가정 1

  1. 구글 로그인 페이지로 접속을 시도한 링크가 잘못 되었다.

특히 application.yaml 파일의 클라이언트 ID와 비밀번호는 올바르게 기재한 것을 확인했으므로, 링크에 포함된 REDIRECT_URI를 의심하기 시작했다.

따라서 google oauth2 Access 또는 redirect_uri_mismatch 라는 키워드로 검색 + redirection_uri을 자세히 확인했다.

3. Action

스택 오버플로우의 글을 참고한 결과, https가 아닌 http로 redirection_uri가 구성되어야 한다는 글을 발견하였다.

따라서 redirection_uri를 다음과 같이 변경해주고, 다시 시도했다.

redirection_url = http://localhost:8080/login/oauth2/code/google

그리고, redirect_uri_mismatch라는 오류 없이 구글 로그인 페이지에 쉽게 접속하는 건 성공하였다.

💡 Tip) 구글 로그인 페이지에 접속하는 링크를 CLIENT_ID 및 REDIRECT_URI까지 수정한 후, 메모장에 붙여놓으면 하이퍼 링크가 깨지지 않아 재사용이 가능하다.

하지만, 로그인을 계속 버튼을 통해 진행할 경우, invalid_scope라는 새로운 오류를 마주했다.

가정 2

이 오류는 특이하게 내가 참고한 블로그의 댓글에서만 확인할 수 있었고, 구글 검색 시 유의미한 포스트는 찾아볼 수 없었다.

결국, invalid_scope라는 오류를 안내해주는 페이지 자체에서 글을 계속 유심히 보던 중..

해결

invalid=[https://www.googleapis.com/auth/userinfo.emailhttps://www.googleapis.com/auth/userinfo.profile]} 이 부분에서 보이는 것처럼, 두 스코프 사이에 공백이 없는 것을 발견!

즉, emailhttps 부분이 어색해서 email https로 수정해주었더니.. 정상적으로 로그인을 완료할 수 있었다.

그리고, 이후 복사한 url을 메모장에서 확인해보니 %20이라는 문자로 사이가 채워져있었다.

가정 3

그리고, 최종적으로 Access Token이 Intellij의 로그에 잘 떴나~ 확인했으나, 이상한 login 페이지로 리다이렉션을 확인했다.

그리고 AccessDeniedException 오류를 마주했다.

해결

결론부터 말하자면 다음과 같다.

oauth2는 spring security를 사용하거나 / 사용하지 않고 구현할 수 있다.

이 중, 나는 구현이 목표였기에 spring security를 사용하지 않는 방향으로 개발하고 있었다.

하지만, spring.io에서 주입받은 security 의존성 때문에 스프링 부트는 계속해서 security를 사용하여 oauth를 구현하는 것으로 인식했던 것이다.

따라서 다음과 같이, security 관련된 의존성을 모두 주석처리하고 나서야 해결할 수 있었다.

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.3'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'yoonNeun'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('springCloudGcpVersion', "5.0.4")
	set('springCloudVersion', "2023.0.0")
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	// implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server'
	// implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
	// implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
	// implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	// implementation 'com.google.cloud:spring-cloud-gcp-starter'

	compileOnly 'org.projectlombok:lombok'

	developmentOnly 'org.springframework.boot:spring-boot-docker-compose'

	runtimeOnly 'com.h2database:h2'
	runtimeOnly 'com.mysql:mysql-connector-j'

	annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
	annotationProcessor 'org.projectlombok:lombok'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// testImplementation 'org.springframework.security:spring-security-test'
}

dependencyManagement {
	imports {
		mavenBom "com.google.cloud:spring-cloud-gcp-dependencies:${springCloudGcpVersion}"
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

4. Result

디버깅만 총 3시간정도 했지만, 로그 상에서 정상적으로 Access Token을 발급받은 것을 확인하였다. 끝!

profile
안녕하세요, 개발자 비비입니다.

0개의 댓글