API 서버와 OAuth2 서버를 분리
앞서 포스팅 작성한 예제까지는 API 서버와 OAuth2 서버가 하나의 웹 애플리케이션에서 같이 올라가는 형태를 취하고 있다. 예제 자체를 심플하게 유지하려는 목적과 이렇게 개발도 가능하다는 것을 보여주기 위한 것이었다.
하지만 실제 서비스를 하기 위해서는 각각 다른 인스턴스 형태로 서비스를 해야 한다.
이전 포스팅에도 언급했지만 보통 API 서버 같은 경우 자주 갱신되어 배포될 일이 잦기 때문에 OAuth2 인증 서버와 같이 운영하기에는 부담이 있다. OAuth2 인증 서버 같은 경우에는 서비스가 중단되었을 때에는 연관된 모든 서비스가 인증관련으로 문제가 발생하게 된다.
그렇기 때문에 API 서비스의 인스턴스와 OAuth2 서버의 인스턴스를 각각 생성시킬 수 있도록 하여 안정성을 늘려야 한다. 물론 트래픽 분산의 효과도 같이 따라온다.
서문은 길었지만 결론은 제대로 된 서비스 하기 위해서는 API 서버와 OAuth2 서버를 분리해야 된다는 이야기이다.
그리고 아래의 소스는 기존 소스의 브랜치로 해두었다. ( 브랜치 링크 )
먼저 기존 소스를 살펴보자.
// https://github.com/sbcoba/spring-boot-oauth2-sample/blob/master/src/main/java/com/example/DemoApplication.java
@EnableResourceServer
@EnableAuthorizationServer
@SpringBootApplication
public class DemoApplication extends ResourceServerConfigurerAdapter {
...
}
위의 소스에서 어노테이션을 잘 확인해보자.
@EnableResourceServer 어노테이션은 API 서버를 설정하기 위한 부분이다.
@EnableAuthorizationServer 어노테이션은 OAuth2 서버를 설정하기 위한 부분이다.
"extends ResourceServerConfigurerAdapter" 이름을 보면 @EnableResourceServer와 이름이 같은 것을 보면 같은 세트(?)라고 예측할 수 있다.
먼저 위의 설정을 OAuth2를 위한 설정과 API 서버를 위한 설정으로 각각 나누어 보자.
/**
* API 서버
*/
@Configuration
@EnableResourceServer
class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
...
}
/**
* OAuth2 서버 설정
*/
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfiguration {
...
}
한 곳에 있던 설정을 따로 두개의 클래스를 만들어서 도출시켰다.
그리고 이전 포스팅에서 설정한 JdbcTokenStore 부분은 OAuth2 서버에서만 사용하게 된다. API 서버에서는 OAuth2 서버의 Token조회 API를 통해서 토큰을 조회하게 된다.
/**
* API 서버
*/
@Configuration
@EnableResourceServer
class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
...
}
/**
* OAuth2 서버 설정
*/
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfiguration {
...
@Bean
public TokenStore JdbcTokenStore(DataSource dataSource) {
return new JdbcTokenStore(dataSource);
}
}
그 이외에 설정은 대부분 API 서버 관련 설정이기 때문에 ResourceServerConfiguration 설정 부분으로 옮기면 된다.
그 다음 단계는 이제 프로젝트를 나눌 것이다.
지금 프로젝트에서 oauth2-server, api-server 형태로 폴더를 추가하여 아래와 같은 형태로 만들었다.
각각 폴더마다 스프링 부트 형태의 구조로 만든 다음 위에서 나눈 설정 형태로 해둔다.
그리고 예제를 로컬에서 실행되기 위해서는 포트가 각각 달라야 한다.
포트 설정할 때에는 중복만 안되면 된다.
여기 예제에서는 OAuth2 서버는 8080 (기본 포트), API 서버는 8081로 설정 후 사용할 예정이다.
(만약에 포트가 변경하면 설정 부분에서도 변경이 필요하다.)
OAuth2 서버의 포트: 8080
API 서버 포트: 8081
@EnableAuthorizationServer
@SpringBootApplication
public class OAuth2Application {
@Bean
public TokenStore jdbcTokenStore(DataSource dataSource) {
return new JdbcTokenStore(dataSource);
}
// ...
}
API 서버에서 Token의 정보를 가져가기 위한 요청을 활성화시켜 줘야 한다.
(기본은 비활성화되어 있다. OAuth2 서버와 API 서버가 같이 있을 때에는 필요 없는 부분이기 때문이다.)
# OAuth2 서버의 application.yml
...
# Token 정보를 API(/oauth/check_token)를 활성화 시킨다. ( 기본은 denyAll )
security.oauth2.authorization.check-token-access: isAuthenticated()
...
@EnableResourceServer
@SpringBootApplication
public class ApiApplication {
@Bean
public ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() {
//...
}
//...
}
전체 적인 구조는 메이븐(maven) 멀티 모듈 형태로 이루어진 형태로 자세한 부분을 github를 참고하면 된다.
API 서버에서는 따로 OAuth2 서버로부터 Access Token 정보를 얻어 와야 되기 때문에 관련 부분을 설정해야 한다.
# API 서버의 application.yml
# 서버 포트 설정
server.port: 8081
# OAuth2 서버에서 기본적으로 Token정보를 받아오는 URL
security.resource.token-info-uri: http://localhost:8080/oauth/check_token
DB는 이전 포스팅처럼 하나의 인스턴스에서는 편의상 하나의 DB에서 API를 위한 테이블과 OAuth2 서버에서 사용하는 데이터(Access Token관리 등)를 위한 위한 테이블을 관리했지만 OAuth2 서버와 API 서버가 나누어지면서 DB도 나누려고 한다. ( 만약 하나로 관리하려고 하면 외부의 DB를 사용해야 한다. )
프로젝트 자체가 나누어진 지기 때문에 따로 설정하지 않아도 각각 H2 DB인스턴스 생성되기 때문에 따로 설정하지 않아도 된다.
서버 실행하는 방법은 Maven(이하 메이븐)을 사용하며, 멀티 모듈이기 때문에 아래와 같이 하면 된다.
# 부모 프로젝트 폴더에 들어간 후
# Oauth2 서버 실행 ( 포트 8080 )
mvn clean -pl oauth2-server spring-boot:run &
# API 서버 실행 ( 포트 8081 )
mvn clean -pl api-server spring-boot:run &
# 포트 변경이 필요하면 아래와 같은 옵션 추가 후 서버 실행
mvn clean -pl api-server spring-boot:run -Dserver.port=9999 &
테스트 방법은 이전 포스팅과 동일하게 진행할 것이다.(어차피 같은 OAuth2 서버이다.)
다른 점이라면 OAuth2 서버와 API를 호출 시에는 서로 다른 서버를 호출하는 정도이다. (로컬이라면 포트만 다르기 때문에 포트만 변경하여 호출하면 된다.)
Access Token 발급
$ curl -F "grant_type=client_credentials" -F "scope=read" "http://foo:bar@localhost:8080/oauth/token"
이전 포스팅과 OAuth2 서버와 포트가 같기 때문에 요청 와 응답 부분도 동일하다.
{
"access_token":"6dfb79ab-46cc-49ad-9b46-b4da66e9e103",
"token_type":"bearer",
"expires_in":42760,
"scope":"read"
}
Access Token과 함께 API 서버에서 API 호출
$ curl -H "Authorization: Bearer 6dfb79ab-46cc-49ad-9b46-b4da66e9e103" "http://localhost:8081/members"
이전 포스팅에서 API 호출하는 부분과 동일하지만 포트 부분이 다른 것을 반드시 확인해야 한다.
이 모든 소스는 기존 소스의 Github의 브랜치로 해두었다. 소스 링크
이제까지 포스팅은 H2 DB를 프로젝트에 내장하여 사용하는 형태를 가지고 개발을 하였다. 그런데 H2 DB 자체가 외부로 실행시켜서 접속하는 형태가 있다. 그런 방법을 간단하게 설명해보겠다.
1. OSX 계열 ( homebrew 사용 )
$ brew install h2
$ h2
2. Windows 계열
http://www.h2database.com/html/main.html
이 사이트에서 아래의 영역에 있는 Windows Installer 링크를 선택하여 다운로드한다.
3. 그 이외에 운영체제 (리눅스 계열 및 OSX 포함)
위의 Windows와 동일한 링크에서 All Platforms 링크를 선택하여 다운로드한다.
압축을 푼 후 해당 디렉터리로 이동하여 아래와 같이 실행시키면 된다.
$ ./bin/h2.sh
서블릿을 통해 할 수 있는 방법도 있으나 스프링 부트를 통하면 더욱 쉽게 H2 서버만 실행할 수 있다. 소스
//...
@SpringBootApplication
public class H2Application {
@Bean public DbStarter dbStarter() {
return new DbStarter();
}
@Bean
public ServletContextInitializer initializer() {
return sc -> {
sc.setInitParameter("db.user", "sa");
sc.setInitParameter("db.password", "");
sc.setInitParameter("db.tcpServer", "-tcpAllowOthers");
};
}
// ...
}
H2 DB에 접속하고자 하는 스프링 부트 애플리케이션은 아래와 같이 설정하여 접속이 가능하다. (접속 정보만 맞으면 어디서나 접속 가능하다. 즉 다른 DB와 접근방법이 동일하다.)
# application.xml
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/api;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driverClassName: org.h2.Driver
username: sa
password:
다음 포스팅: 7. JWT 방식으로 바꿔 보자
이전 포스팅: 5. OAuth2 서버를 커스터마이징 해보자(TokenStore 편)
1. 스프링 부트와 OAuth2 (https://brunch.co.kr/@sbcoba/1 )
2. 본격적인 개발 하기전에 (https://brunch.co.kr/@sbcoba/2 )
3. API 서버 만들기 (https://brunch.co.kr/@sbcoba/3 )
4. 간단한 OAuth2 서버 만들어 보기 (https://brunch.co.kr/@sbcoba/4 )
5. OAuth2 서버를 커스터마이징 해보자(TokenStore 편) (https://brunch.co.kr/@sbcoba/5 )
6. API 서버와 OAuth2 서버를 분리 (https://brunch.co.kr/@sbcoba/6 )
7. JWT 방식으로 바꿔 보자 (https://brunch.co.kr/@sbcoba/7 )
8. OAuth2 서버를 커스터마이징 해보자(클라이언트 관리 편) (https://brunch.co.kr/@sbcoba/8 )
9. OAuth2 시스템에서 Scope를 이용한 API 권한 제어 (https://brunch.co.kr/@sbcoba/15 )