OAuth2 프레임워크
권한 부여 프레임워크라고 부르는 경우가 많으며, 타사 웹사이트나 웹이 리소스에 접근할 수 있게 허용하는 것이 주된 목적이다.
OAuth2는 특정 구현이나 라이브러리가 아니다.
참고) Http Basic 문제점: 모든 요청에 자격 증명을 보내야 한다
- 네트워크를 통해 자격 증명이 자주 공유된다.
- 클라이언트가 자격 증명을 저장해 인증 및 권한 부여 요청과 함께 자격 증명을 서버에 보낼 수 있게 한다.
자격 증명을 관리하는 책임을 시스템의 한 구성 요소에 격리한다면 관리하기 쉬워진다.
OAuth2 인증 아키텍처의 구성요소
구성 요소

- 리소스 서버: 사용자가 소유한 리소스를 호스팅 하는 서버. 리소스는 사용자의 데이터이거나 사용자가 수행할 수 있는 작업일 수 있다.
- 사용자: 리소스 서버가 노출하는 리소스를 소유하는 개인. 일반적으로 사용자는 사용자 이름과 암호로 신원을 증명한다.
- 클라이언트: 사용자를 대신해 사용자가 소유한 리소스에 접근하는 애플리케이션. 클라이언트는 클라이언트 ID와 클라이언트 비밀을 이용해 신원을 증명한다. 클라이언트는 요청할 때 자신을 증명하는 자체 자격 증명이 필요하다.
- 권한 부여 서버: 클라이언트가 리소스 서버가 노출하는 사용자의 리소스에 접근할 권한을 부여하는 애플리케이션. 권한 부여 서버는 클라이언트가 사용자 대신 리소스에 접근 권한이 있다고 결정하면 토큰을 발급한다. 클라이언트는 이 토큰을 이용해 권한 부여 서버에서 권한을 받았음을 리소스 서버에 증명한다.
OAuth2를 구현하는 방법 선택
OAuth2에는 여러 인증흐름이 있어, 자신의 아키텍처에 맞는 방식으로 구현해야 한다.
OAuth2에는 그랜트(grant)라는 토큰을 얻는 여러 방안을 제공한다.
- 승인 코드
- 암호
- 갱신 토큰
- 클라이언트 자격 증명
승인 코드 그랜트 유형의 구현
가장 많이 이용되는 OAuth2 흐름 중 하나이다.
동작 순서
- 인증 요청을 한다.
- 클라이언트는 사용자가 인증해야 하는 권한 부여 서버의 엔드포인트로 사용자를 리다이렉션 한다.
- 사용자가 권한 부여 서버와 직접 상호작용하며, 클라이언트가 앱으로 자격 증명을 보내는 것이 아니다.
- 사용자를 권한 부여 서버로 리다이렉션 할 때 클라이언트는 아래 세부 정보가 포함된 요청 쿼리로 권한 부여 엔드포인트를 호출
- response_type: 클라이언트가 코드를 기대한다는 것을 권한 부여 서버에 알리는 값인 Code를 포함한다.
- client_id: 애플리케이션 자체를 식별하는 클라이언트 id 값
- redirect_uri: 인증 성공 후 사용자를 리다이렉션 할 위치를 권한 부여 서버에 알려준다.
- scope: 허가된 권한
- state: CSRF 보호를 위한 CSRF 토큰을 정의한다.
- 인증에 성공하면 리다이렉트로 클라이언트를 호출. 코드와 상태 값을 제공한다.
- 클라이언트는 상태 값이 요청에 보낸 것과 같은지 검사해 다른 사람이 리다이렉션 URI를 호출하려는 것이 아닌지 확인
- 액세스 토큰을 얻는다.
- 클라이언트는 토큰을 얻기 위해 코드로 권한 부여 서버를 호출한다.
- 인증 서버를 두 번 호출하는 이유와 두 가지 다른 토큰이 필요한 이유
- 권한 부여 서버는 사용자가 직접 상호작용했다는 증거로 첫 번째 코드를 생성한다. -> 클라이언트는 이 코드를 받고 자신의 자격 증명과 함께 이용해 액세스 토큰을 받기 위한 인증을 한다.
- 클라이언트는 이 토큰을 이용해 리소스 서버의 리소스에 접근한다.
- 권한 부여 서버가 사용자에게 곧바로 액세스 토큰을 반환하지 않는 건 이러한 방식이 권장되지 않기 때문이다.(흐름이 안전하지 X)
- 클라이언트가 권한 부여 서버에 요청하고 이 요청에 아래와 같은 세부 사항이 들어간다.
- code: 승인 코드
- client_id, client_secret: 클라이언트의 자격 증명
- redirect_uri: 검증에 사용
- grant_type: 이용된 흐름의 유형을 식별하여 authorization_code 값을 가진다.
- 보호된 리소스를 호출한다
- 권한 부여 서버에서 액세스 토큰을 받고 나면 클라이언트는 보호된 리소스를 호출할 수 있다.
- 클라이언트는 리소스 서버의 엔드포인트를 호출할 때 권한 부여 요청 헤더의 액세스 토큰을 사용한다.
참고: 승인 코드 그랜트 유형은 사용자가 자신의 자격 증명을 클라이언트와 공유하지 않고도 클라이언트가 특정 작업을 실행하도록 허가할 수 있는 장점이 있다. 단 약점이 있는데 누군가 승인 코드를 가로채면 문제가 된다. (클라이언트의 자격 증명도 도난당했다고 가정)
해당 취약점을 완화하려면 PKCE 승인 코드 그랜트 유형을 이용하는 더 복잡한 시나리오에 의존해야 한다.
암호 그랜트 유형 구현
리소스 소유자 자격 증명 그랜트 유형이라고도 하며, 애플리케이션은 클라이언트가 사용자 자격 증명을 수집하고 이를 이용해 인증하며 권한 부여 서버에서 액세스 토큰을 얻는다.
해당 인증 흐름은 클라이언트와 권한 부여 서버를 같은 조직에서 구축하고 유지 관리할 때만 이용한다.
- 액세스 토큰을 요청한다.
- 클라이언트는 사용자 자격 증명을 수집하고 권한 부여 서버를 호출해 액세스 토큰을 얻는다.
- 클라이언트는 액세스 토큰을 요청할 때 아래 세부 정보를 함께 보낸다.
- grant_type: Password 값을 가진다.
- client_id, client_secret: 클라이언트가 자신을 인증하기 위한 자격 증명
- scope: 허가된 권한
- username, password: 사용자 자격 증명. 일반 텍스트 형식으로 요청 헤더의 값으로 전송된다.
- 액세스 토큰을 이용해 리소스를 호출한다.
- 액세스 토큰을 얻은 클라이언트는 이 토큰으로 리소스 서버의 엔드포인트를 호출할 수 있으며, 승인 코드 그랜트 유형을 이용할 때와 마찬가지로 권한 부여 요청 헤더에 액세스 토큰을 추가한다.
여기서 핵심은 리소스 소유자가 클라이언트를 신뢰할 때만 해당 구현이 가능하다.
클라이언트 자격 증명 그랜트 유형 구현
OAuth2가 제공하는 가장 단순한 그랜트 유형이며, 두 애플리케이션 간의 인증을 구현할 때 이용할 수 있다.
자격 증명 그랜트 유형과 비슷하며, 차이점은 액세스 토큰을 요청할 때 사용자 자격 증명이 필요하지 않다는 것이다.
- 액세스 토큰을 요청한다.
- 클라이언트는 액세스 토큰을 요청할 때 아래 세부 정보를 함께 보낸다.
- grant_type: client_credentials 값을 가진다.
- client_id, client_secret: 클라이언트 자격 증명을 나타낸다.
- scope: 허가된 권한
- 클라이언트는 액세스 토큰을 요청할 때 아래 세부 정보를 함께 보낸다.
- 액세스 토큰을 이용해 리소스를 호출한다.
- 액세스 토큰을 얻은 클라이언트는 이 토큰으로 리소스 서버의 엔드포인트를 호출할 수 있으며, 승인 코드 그랜트 유형을 이용할 때와 마찬가지로 권한 부여 요청 헤더에 액세스 토큰을 추가한다.
갱신 토큰으로 새 액세스 토큰 얻기
토큰은 수명을 짧게 가지고 있어야 토큰이 분실되어도 해당 토큰이 만료되면 더 이상 이용할 수 없이 새로운 액세스 토큰을 발급받아야 한다.
권한 부여 서버는 재인증할 필요를 없애기 위해 액세스 토큰과는 값과 용도가 다른 갱신 토큰을 발행할 수 있고, 앱은 갱신 토큰으로 재인증 없이 새 액세스 토큰을 얻을 수 있다.
사용자 자격증명을 저장하는 것이 아니라 갱신 토큰을 저장하고 필요할 때 이용해 새 액세스 토큰을 얻을 수 있다. 갱신 토큰은 노출된 것이 확인되면 취소할 수 있으므로 더 안전하다.
- 권한 부여 서버가 승인 코드나 암호 그랜트 유형과 같은 흐름을 사용할 때 액세스 토큰과 함께 갱신 토큰을 반환한다.
- 클라이언트 자격 증명 그랜트 유형에는 사용자 자격 증명이 필요 없으므로 갱신 토큰도 사용되지 X
- 갱신 토큰을 가진 클라이언트는 액세스 토큰이 만료될 때 아래 세부 정보가 포함된 요청을 발행
- refresh_token 값을 가지는 grant_type
- 갱신 토큰의 값을 가지는 refresh_token
- 클라이언트의 자격 증명을 포함하는 client_id와 client_secret
- 같거나 더 작은 허가 권한을 정의하는 scope
- 더 많은 허가 권한 부여 시 재인증 필요
OAuth2의 허점
상황
- 클라이언트에서 CSRF 이용: 애플리케이션이 CSRF 보호 메커니즘을 적용하지 않으면, 사용자가 로그인했을 때 CSRF가 가능하다.
- 클라이언트 자격 증명 도용: 보호되지 않은 자격 증명을 저장하거나, 전송하면 이를 공격자가 도용하는 위반이 발생할 수 있음
- 토큰 재생: 토큰은 Oauth2 인증 및 권한 부여 아키텍처에서 리소스에 액세스 할 때 쓰는 열쇠다. 이 열쇠는 네트워크를 통해 보내는 동안 누군가가 가로채고, 재사용될 수 있다.
- 토큰 하이재킹: 인증 프로세스를 방해하고 리소스에 액세스 하기 위한 토큰을 훔치는 것을 의미한다. 이는 갱신 토큰을 이용하는 잠재적인 취약성이기도 하며 갱신토큰을 가로채고 새 액세스 토큰을 얻는 데 이용할 수 있기 때문이다.
SSO 애플리케이션 구현
권한 부여 서버 관리
권한 부여 서버로 깃허브를 이용
https://github.com/settings/applications/new
GitHub · Build and ship software on a single, collaborative platform
Join the world's most widely adopted, AI-powered developer platform where millions of developers, businesses, and the largest open source community build software that advances humanity.
github.com


- 홈페이지와 callbackUrl 은 모두 localhost로 지정한다.
- 클라이언트 ID와 클라이언트 비밀키를 제공한다.
Build.gradle(종속성 추가)
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
MainController
@Controller
public class MainController {
@GetMapping("/")
public String main(){
return "main.html";
}
}
Config
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login(); //인증 메서드 설정
http.authorizeRequests()
.anyRequest().authenticated();
}
}
- OAuth2Login()은 간단하게 필터 체인에 새 인증 필터를 추가한다.
- 해당 메서드를 호출할 때 프레임워크가 OAuth2LoginAuthenticationFilter를 필터 체인에 추가한다.
- 해당 필터가 요청을 가로채서 OAuth2 인증에 필요한 논리를 적용한다.
ClientRegistraion
ClientRegistraion 인터페이스는 OAuth2 아키텍처의 클라이언트를 나타내며 아래와 같은 클라이언트의 모든 세부 정보를 정의해야 한다.
- 클라이언트 ID와 비밀
- 인증에 이용되는 그랜트 유형
- 리다렉션 URI
- 범위
추가로 아래와 같이 권한 부여 서버의 URL을 제공해야 한다.
- 권한 부여 URI: 클라이언트가 인증을 위해 사용자를 리다이렉션 하는 URI
- 토큰 URI: 클라이언트가 액세스 토큰과 갱신 토큰을 얻기 위해 호출하는 URI
- 사용자 정보 URI: 클라이언트가 액세스 토큰을 얻은 후 사용자의 세부 정보를 얻기 위해 호출하는 URI
권한 부여 서버를 직접 구현하지 않는다면 해당 정보는 모두 권한 부여 서버의 설명서에서 얻어야 한다.
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
Authorizing OAuth apps - GitHub Docs
You can enable other users to authorize your OAuth app.
docs.github.com
Spring Security에서는 더 편하게 작업할 수 있도록 CommonOAuth2Provider라는 클래스를 정의하여 아래 요소를 포함한 가장 일반적인 공급자에 대한 인증에 이용할 수 있는 ClientRegistration 인스턴스를 부분적으로 정의한다.
- 구글
- 깃허브
- 페이스북
- 옥타
인증 부여 서버가 일반적인 공급자가 아니라면 ClientRegostration을 재정의하는 것 외에는 다른 방법이 없다.
private ClientRegistration clientRegistration(){
return CommonOAuth2Provider.GITHUB
.getBuilder("github")
.clientId("Ov23liuBovHUCItZ2vPK")
.clientSecret("5b7e10c0183074c5df3ca90262b19ee1ds8d83a8")
.build();
}
ClientRegistraionRepository
- ClientRegostration 인스턴스를 스피링 시큐리티에 등록하는 방법
@Bean
public ClientRegistrationRepository clientRepository(){
ClientRegistration clientRegistration = clientRegistration();
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration clientRegistration(){
return CommonOAuth2Provider.GITHUB
.getBuilder("github")
.clientId("Ov23liuBovHUCItZ2vPK")
.clientSecret("5b7e10c0183074c5df3ca90262b19ee1ds8d83a8")
.build();
}
- ClientRegistraionRepository에는 하나 이상의 ClientRegostration 객체가 있다.(ClientRegistraionRepository 객체는 등록 ID로 ClientRegostration을 찾는다)
- 스프링 시큐리티는 ClientRegostration의 인스턴스를 메모리에 저장하는 ClientRegostrationRepository의 구현인 InmemoryClientRegistraionRepository를 제공한다.
HttpSecurity 객체의 oauth2Login() 메서드의 매개 변수로 Customizer 객체 이용
@Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login( c -> {
c.clientRegistrationRepository(clientRepository());
});
http.authorizeRequests()
.anyRequest().authenticated();
}
private ClientRegistrationRepository clientRepository(){
ClientRegistration clientRegistration = clientRegistration();
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration clientRegistration(){
return CommonOAuth2Provider.GITHUB
.getBuilder("github")
.clientId("Ov23liuBovHUCItZ2vPK")
.clientSecret("5b7e10c0183074c5df3ca90262b19ee1ds8d83a8")
.build();
}
둘 중 하나의 방식만 사용하는 것을 권장
스프링 부트 구성의 순수한 마법
application.properties로 구현
spring.security.oauth2.client.registration.github.client-id=Ov23liuBovHUCItZ2vPK
spring.security.oauth2.client.registration.github.client-secret=5b7e10c0183074c5df3ca90262b19ee1ds8d83a8
- 클라이언트 ID와 시크릿만 설정하면 된다.
- 공급자 이름이 github이기 때문에 CommonOAuth2Provider 클래스에서 URI와 관련된 모든 세부 정보를 가져오는 방법을 알고 있다.
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login();
http.authorizeRequests()
.anyRequest().authenticated();
}
}
스프링 시큐리티에 알려진 일반적인 공급자가 아닌 다른 공급자를 이용하려면 spring.security.oauth2.client.provider로 시작하는 속성 그룹으로 권한 부여 서버에 관한 세부 정보도 지정해야 한다.
인증된 사용자의 세부정보 얻기
@Controller
public class MainController {
@GetMapping("/")
public String main(OAuth2AuthenticationToken token) {//스프링 부트는 사용자를 나타내는 Authentication 객체를 자동으로 메서드의 매개변수에 주입한다.
return "main.html";
}
}
- 프레임워크에 이용되는 Authentication 객체의 구현은 OAuth2AuthenticationToken이며, SecurityContext에서 직접 가져오거나 스프링 부트가 엔드포인트의 매개 변수에 주입하게 할 수 있다
'스프링 시큐리티 인 액션' 카테고리의 다른 글
| 발표 (0) | 2025.05.05 |
|---|---|
| Ch11. 실전. 책임의 분리 (0) | 2025.04.25 |
| Ch10-2. CORS 이용 (0) | 2025.04.24 |