주의
이 책은 Spring Security 5 버전을 기준으로 작성되었으므로, Spring Boot 3.X 버전에서는 일부 클래스가 더 이상 사용되지(deprecated) 않을 수 있습니다.
서론
- 권한 부여 서버의 역할은 사용자를 인증하고 클라이언트에 토큰을 제공하는 것
- 클라이언트는 리소스 서버가 노출하는 리소스에 사용자를 대신해 접근하는 데 해당 토큰을 이용
- OAuth 2 프레임워크는 토큰을 얻기 위한 여러 흐름을 정의
- 이러한 흐름을 grant라고 하며 시나리오에 맞게 여러 그랜트 유형 중 하나를 선택할 수 있음
- 권한 부여 서버의 동작은 선택한 그랜트 유형에 따라 달라짐
- 책이 출판되던 당시에는 Spring Security의 OAuth 2 종속성 지원이 중단된 상태였기 때문에, 이번 장에서는 Spring Security를 이용해 커스텀 인증 서버를 개발할 수 있는 유일한 방법이었던 '커스텀 권한 서버 구현'에 대해 다룸
- 현재는 지원되는 것으로 확인됨 (https://docs.spring.io/spring-authorization-server/reference/index.html)
Spring Authorization Server Reference :: Spring Authorization Server
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
docs.spring.io
1. 맞춤형 권한 부여 서버 구현 작성
- OAuth 2에서 가장 중요한 것은 액세스 토큰을 얻는 것이며 OAuth 2 아키텍처에서 액세스 토큰을 발행하는 구성 요소가 바로 권한 부여 서버
- 이를 구현하기 위해 AuthServerConfig라고 명명한 구성 클래스를 정의하는데 해당 클래스에는 기존의 @Configuration 어노테이션 외에도 @EnableAuthorizationServer 어노테이션을 지정해야 함
- 이렇게 하면 스프링 부트에 OAuth 2 권한 부여 서버에 관한 구성을 활성화하도록 지시할 수 있음
- AuthorizationServerConfigurerAdapter 클래스를 확장하고 이 단원에서 논의할 특정한 메서드를 재정의해서 해당 구성을 맞춤 구성할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableAuthorizationServer | |
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { | |
} |
2. 사용자 관리 정의
- 권한 부여 서버는 OAuth 2 프레임워크에서 사용자 인증을 담당하는 구성 요소이므로 자연스럽게 사용자를 관리하는 일도 함
- 아래 그림은 스프링 시큐리티의 인증 프로세스를 수행하는 주요 구성 요소를 다시 정리한 것
- 지금까지 다룬 인증 아키텍처와 달리 SecurityContext가 없다는 것을 확인할 수 있는데 이렇게 달라진 이유는 이제 인증의 결과가 SecurityContext에 저장되지 않고 TokenStore의 토큰으로 관리되기 때문
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Authentication 인스턴스에 접근하기 위해 WebSecurityConfigurerAdapter 구현 | |
@Configuration | |
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { | |
@Bean | |
public UserDetailsService uds() { | |
var uds = new InMemoryUserDetailsManager(); | |
var u = User.withUsername("john") | |
.password("12345") | |
.authorities("read") | |
.build(); | |
uds.createUser(u); | |
return uds; | |
} | |
@Bean | |
public PasswordEncoder passwordEncoder() { | |
return NoOpPasswordEncoder.getInstance(); | |
} | |
// AuthenticationManager 인스턴스를 스프링 컨텍스트에 빈으로 추가 | |
@Override | |
@Bean | |
public AuthenticationManager authenticationManagerBean() throws Exception { | |
return super.authenticationManagerBean(); | |
} | |
} | |
@Configuration | |
@EnableAuthorizationServer | |
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { | |
@Autowired | |
private AuthenticationManager authenticationManager; | |
// AuthenticationManager를 설정하도록 configure() 메서드 재정의 | |
@Override | |
public void configure( | |
AuthorizationServerEndpointsConfigurer endpoints) { | |
endpoints.authenticationManager(authenticationManager); | |
} | |
} |

3. 권한 부여 서버에 클라이언트 등록
- OAuth 2 아키텍처에서 클라이언트로 작동하는 앱이 권한 부여 서버를 호출하려면 자체 자격 증명이 필요함
- 권한 부여 서버도 이러한 자격 증명을 관리하며 알려진 클라이언트의 요청만 수락함

- 앞서 12장에서 개발한 클라이언트 애플리케이션에서는 깃허브를 인증 서버로 이용했음
- 그러려면 깃허브에 우리 클라이언트 앱을 알려야 하므로 먼저 깃허브에 애플리케이션을 등록하고 클라이언트 자격 증명인 클라이언트 ID와 클라이언트 비밀을 받았음
- 이러한 자격 증명을 구성하고 앱에서 권한 부여에 인증하는데 이용했음
- 이번에도 같은 원칙이 적용되며 권한 부여 서버는 알려진 클라이언트의 요청만 받으므로 먼저 클라이언트를 알려줘야 함
- 권한 부여 서버에서 클라이언트를 정의하는 일은 ClientDetails 계약이 담당함
- 해당 ID로 ClientDetails를 검색하는 객체를 정의하는 계약은 ClientDetailsService
- 이름에서도 알 수 있다시피 앞서 다룬 UserDetails 및 UserDetailsService 인터페이스와 비슷하게 작동함
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableAuthorizationServer | |
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { | |
// 중략 | |
// ClientDetailsService 인스턴슬르 설정하도록 configure() 메서드 재정의 | |
@Override | |
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { | |
// ClientDetailsService 구현을 이용해 인스턴스 생성 | |
var service = new InMemoryClientDetailsService(); | |
// ClientDetails의 인스턴스를 만들고 클라이언트 관련 필수 세부 정보 설정 | |
var cd = new BaseClientDetails(); | |
cd.setClientId("client"); | |
cd.setClientSecret("secret"); | |
cd.setScope(List.of("read")); | |
cd.setAuthorizedGrantTypes(List.of("password")); | |
// InMemoryClientDetailsService에 ClientDetails 인스턴스 추가 | |
service.setClientDetailsStore( | |
Map.of("client", cd)); | |
clients.withClientDetails(service); | |
} | |
} |

4. 암호 그랜트 유형 (Password Grant Type) 이용
- 스프링 시큐리티가 자동으로 구성해 주는 엔드포인트인 /oauth/token에 토큰을 요청할 수 있음
- HTTP Basic으로 클라이언트 자격 증명을 이용해 엔드포인트에 접근하고 필요한 세부 정보를 쿼리 매개변수로 전달
- grant_type: password 값을 가짐
- username 및 password: 사용자 자격 증명
- scope: 허가된 권한
- 클라이언트는 리소스 서버가 노출하는 리소스를 응답으로 받은 토큰을 이용해 호출할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// request | |
"curl -v -XPOST -u client:secret http://localhost:8080/oauth/token?grant_type=password&username=john&password=12345&scope=read" | |
// response | |
{ | |
"access_token":"693e11d3-bd65-431b-95ff-a1c5f73aca8c", | |
"token_type":"bearer", | |
"expires_in":42637, | |
"scope":"read" | |
} |

5. 승인 코드 그랜트 유형 (Authorization Code Grant Type) 이용
- 승인 코드 그랜트 유형의 경우 다른 그랜트 유형과 달리 리다이렉트 할 URI도 제공해야 함
- 리다이렉트 할 URI는 권한 부여 서버가 인증을 마친 사용자를 리다이렉트 할 URI
- 권한 부여 서버는 리다이렉트 할 URI를 호출할 때 액세스 코드도 제공함
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableAuthorizationServer | |
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { | |
// 중략 | |
@Override | |
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { | |
clients.inMemory() | |
.withClient("client") | |
.secret("secret") | |
.authorizedGrantTypes("authorization_code") | |
.scopes("read") | |
.redirectUris("http://localhost:9090/home"); | |
} | |
@Override | |
public void configure(AuthorizationServerEndpointsConfigurer endpoints) { | |
endpoints.authenticationManager(authenticationManager); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// request | |
curl -v -XPOST -u client:secret "http://localhost:8080/oauth/token?grant_type=authorization_code&scope=read&code=qeSLSt" | |
// response | |
{ | |
"access_token":"0fa3b7d3-e2d7-4c53-8121-bd531a870635", | |
"token_type":"bearer", | |
"expires_in":43052, | |
"scope":"read" | |
} |

- 각기 다른 허가를 이용하는 여러 클라이언트가 있을 수 있으며 한 클라이언트를 위한 여러 그랜트 유형을 설정하는 것도 가능함
- 권한 부여 서버는 클라이언트의 요청에 따라 적절하게 작동함
- 아래 코드에서 클라이언트별로 다른 허가를 구성하는 방법을 볼 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableAuthorizationServer | |
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { | |
// 중략 | |
@Override | |
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { | |
clients.inMemory() | |
.withClient("client1") | |
.secret("secret1") | |
// ID가 client1인 클라이언트는 승인 코드 그랜트만 이용 가능 | |
.authorizedGrantTypes( | |
"authorization_code") | |
.scopes("read") | |
.redirectUris("http://localhost:9090/home") | |
.and() | |
.withClient("client2") | |
.secret("secret2") | |
// ID가 client2인 클라이언트는 승인 코드, 암호, 갱신 토큰 허가 모두 이용 가능 | |
.authorizedGrantTypes( | |
"authorization_code", "password", "refresh_token") | |
.scopes("read") | |
.redirectUris("http://localhost:9090/home"); | |
} | |
@Override | |
public void configure( | |
AuthorizationServerEndpointsConfigurer endpoints) { | |
endpoints.authenticationManager(authenticationManager); | |
} | |
} |
6. 클라이언트 자격 증명 그랜트 유형 (Client Credentials Grant Type) 이용
- 특정 사용자와 무관하지만 클라이언트가 접근해야 하는 엔드포인트를 보호하는 데도 클라이언트 자격 증명 그랜트 유형을 이용할 수 있음
- 서버의 상태를 알려주는 엔드포인틀르 구현한다고 가정했을 때 클라이언트는 해당 엔드포인트를 호출해 연결을 확인하고 사용자에게 연결 상태나 오류 메시지를 표시함
- 해당 엔드포인트는 클라이언트와 리소스 서버 간의 거래를 나타낼 뿐 사용자별 리소스와는 관련이 없으므로 사용자 인증 없이도 클라이언트가 호출할 수 있어야 함
- 이러한 시나리오에 클라이언트 자격 증명 그랜트 유형을 이용할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableAuthorizationServer | |
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { | |
// 중략 | |
@Override | |
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { | |
clients.inMemory() | |
.withClient("client") | |
.secret("secret") | |
.authorizedGrantTypes("client_credentials") | |
.scopes("info"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// request | |
curl -v -XPOST -u client:secret "http://localhost:8080/oauth/token?grant_type=client_credentials&scope=info" | |
// response | |
{ | |
"access_token":"431eb294-bca4-4164-a82c-e08f56055f3f", | |
"token_type":"bearer", | |
"expires_in":4300, | |
"scope":"info" | |
} |

- 클라이언트 자격 증명 그랜트 유형에서 조심할 점은 클라이언트가 자체 자격 증명을 이용하는 것이 이 그랜트 유형의 유일한 조건이라는 점
- 즉, 사용자 자격 증명이 필요한 흐름과 같은 범위에 대해 접근을 제공하지 않게 해야 함
- 그렇지 않으면 클라이언트가 사용자의 허락 없이 사용자의 리소스에 접근할 수 있게 됨
- 아래 그림은 클라이언트가 사용자 인증 없이도 사용자 리소스 엔드포인트를 호출할 수 있게 하여 개발자 스스로 보안 위반을 초래한 디자인이 나옴

7. 갱신 토큰 그랜트 유형 (Refresh Token Grant Type) 이용
- 갱신 토큰은 승인 코드 그랜트 유형 및 암호 그랜트 유형과 함께 이용할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableAuthorizationServer | |
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { | |
// 중략 | |
@Override | |
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { | |
clients.inMemory() | |
.withClient("client") | |
.secret("secret") | |
// 클라이언트에 승인된 그랜트 유형 목록에 refresh_token 추가 | |
.authorizedGrantTypes( | |
"password", | |
"refresh_token") | |
.scopes("read"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// request | |
curl -v -XPOST -u client:secret http://localhost:8080/oauth/token?grant_type=password&username=john&password=12345&scope=read | |
// response | |
{ | |
"access_token":"da2a4837-20a4-447d-917b-a22b4c0e9517", | |
"token_type":"bearer", | |
"refresh_token":"221f5635-086e-4b11-808c-d88099a76213", | |
"expires_in":43199, | |
"scope":"read" | |
} |

참고
스프링 시큐리티 인 액션
반응형
'Spring > 스프링 시큐리티 인 액션' 카테고리의 다른 글
[15장] OAuth 2: JWT와 암호화 서명 사용 (0) | 2025.06.01 |
---|---|
[14장] OAuth 2: 리소스 서버 구현 (0) | 2025.06.01 |
[12장] OAuth 2가 작동하는 방법 (0) | 2025.05.31 |
[10장] CSRF 보호와 CORS 적용 (0) | 2025.05.31 |
[8장] 권한 부여 구성: 제한 적용 (0) | 2025.05.30 |