brunch

You can make anything
by writing

C.S.Lewis

by 이수홍 Feb 13. 2016

Spring Boot로 만드는 OAuth2 시스템 4

간단한 OAuth2 서버 만들어 보기

이번  포스팅부터 본격적으로 OAuth2 서버를 만들어 보겠다. 

간단한 세팅을 시작으로 하나하나 점증적으로 확장하는 형태로 진행할 예정이다.

(샘플 소스: https://github.com/sbcoba/spring-boot-oauth2-sample/tree/master)


Client ID 계정 생성 

먼저 기본적인 OAuth2 서버에서 가지는 Client  ID와 Client Secret을 아래에서 설정 정보를 추가해보자.

(실제 소스)

# resources/application.properties
security.oauth2.client.client-id=foo
security.oauth2.client.client-secret=bar
# client-id : client를 식별하는 고유 정보
# client-secret : 액세스 토큰을  교환하기 위한 비공개 정보 ( 보통 암호 )

Client의 의미

OAuth2 서버를 통해 API에 접근을 허가한 클라이언트를 지칭하는 명칭이다. 

클라이언트 종류로는 보통 웹, 아이폰 앱, 안드로이드 앱, PC 앱 등이 있다.

예를 들면 페이스북에서도 개발자 사이트에서 클라이언트 계정을 아래의 버튼으로 발급할 수 있다.

페이스 북에서는 스프링 시큐리티 OAuth2의 명칭과 약간 다르지만 동일한 의미를 지닌다고 생각하면 된다.

Client ID -> App  ID

Client Secret -> App Secret

발급된 페이스북 App 계정

보통 OAuth2 인증을 지원하는 웹사이트에서는 클라이언트 계정을 발급해야 하는 관리자 페이지와 그것을 저장하는 저장소 등을 가지고 있는 경우가 많다. 클라이언트 정보 저장소로는 보통 Mysql과 같은 RDB과  NoSQL 같은 외부 저장소를 이용해서 저장해둔다. 하지만 이 같은 경우에는 가장 간단한 샘플 형태이기 때문에 직접 하드 코딩해두는 경우이다.

그래서 뒷부분에서는 확장하는 부분을 설명하면서 다른 저장소를 이용해서 클라이언트 정보를 관리하는 방법도 다룰 예정이다.


소스 작성 (실제 소스)

// DemoApplication.java 
@EnableResourceServer // API 서버 인증(또는 권한 설정
@EnableAuthorizationServer // OAuth2 권한 서버
@SpringBootApplication
public class DemoApplication {
   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
}

OAuth2 인증서버와 API 서버를 만들었다. ( 어노테이션 두개로 말이다!! )

놀랍지 않은가? 어노테이션 두개만  설정했을 뿐인데 설정이 완료되었다. 이것이 바로 스프링 부트의 힘이다!

사실 이번 포스팅에서 코딩은 여기 까지다. ( 테스트용 부분은 제외 )

정말 OAuth2 인증이 되는지 한 번 테스트해보자. 


OAuth2의 Access Token 발급받기 테스트

테스트를 위해 추천하는 툴은 크롬 브라우저의 앱인 Postman을 추천한다. 무료인데도 불구하고 API테스트시에는 쓸만하다고 생각된다. 
물론 linux(unix) 계열 운영체제이면 curl로도 충분히 테스트 가능하다. 둘 다 기술하기 힘들기 때문에 텍스트로 설명이 가능한 curl 기준으로 설명할 예정이다. ( 윈도용 curl이 있는데 직접  설치해야 한다.)


스프링 부트에서 스프링 시큐리티 OAuth2의 기본 설정이 클라이언트의 엑세스 토큰 발급 방법을 다섯 가지 방법으로 받을 수 있도록 활성화되어 있다. 그 다섯 가지 방식에 대해 알아 보고 테스트해보려고한다.

일단 위에서 언급한 github에서 샘플 소스를 받고 서버가 실행한 상태에서 테스트가 가능하다.


1. 권한 코드 방식 (Authorization Code flow)

보통 서버 사이트 웹에서 인증받을 때 가장 많이 쓰는 방식으로 기본적으로 지원하고 있는 방식이다.

엑세스 토큰을 받기 위한 테스트가 다른 방식에 비해 복잡하다. 

(따로 인증 관련 요청 페이지 부분을 생성했다.)

먼저 바로 아래의 주소로 브라우저에서 호출한다. 

http://localhost:8080/oauth/authorize?response_type=code&client_id=foo&redirect_uri=http://localhost:8080/test/authorization-code&scope=read
Client의 접근을 허가 할 것인지 여부를 묻는다. 

위 화면에서 Approve체크 후 하단에 Authorize버튼을 클릭하면 아래의 주소로 리다이렉트 되면서 브라우저 화면에서 curl 명령어가 보일 것이다.

http://localhost:8080/test/authorization-code?code=생성된코드
$ curl -F "grant_type=authorization_code" -F "code=생성된 코드" -F "client_id=foo" -F "scope=read" -F "client_secret=bar" -F "redirect_uri=http://localhost:8080/test/authorization-code" "http://foo:bar@localhost:8080/oauth/token"

브라우저에 나타나 curl 명령어를 복사해서 실행하면 아래의 엑세스 토큰정보가 보일 것이다.

{
  "access_token":"1f94c2eb-99bb-412a-bc17-9630b1ae29dc",
  "token_type":"bearer",
  "refresh_token":"a4b037b7-f736-4bde-a073-7f88279df9bb",
  "expires_in":43199,
  "scope":"read"
}

2. 암묵적인 동의 방식 (Implicit Grant flow)

보통 클라이언트 사이드에서 OAuth2 인증하는 방식이다. 

http://user:test@localhost:8080/oauth/authorize?response_type=token&redirect_uri=http://localhost:8080&client_id=foo&scope=read

위의 주소 형태를 호출하면 redirect_uri에 입력된 주소로 리다이렉트 되면서 기본적으로 해쉬태그(#)에서 파라메터로 아래와 같이 Access Token을  전달해준다. (리프러시 토큰 제공 X)

http://localhost:8080/#access_token=e81131a9-ff4f-4230-a1d7-9c3a86e0f06c&token_type=bearer&expires_in=43199

해쉬토큰(#)으로 보내기 때문에 서버에서는 엑세스 토큰을 받지 못한다. 즉 보안상 엑세스토큰을 프론트에서만 사용하도록 하는 것을 권장하는 형태이다.
참고로 프론트에서 API호출시에는 주로 AJAX 를 통해서 API를 호출 하게 되며 호출 시에 도메인이 같지 않은 경우가 많기 때문에 API 에서는 JSONP, CORS 형태를 지원해야 원활히 통신이 가능하게 된다.


3. 자원 소유자 비밀번호  (Resource Owner Password Credentials flow)

자원 소유자 즉 사용자의 아이디(username)와 비밀번호로 엑세스 토큰 발급한다.

$ curl foo:bar@localhost:8080/oauth/token -d grant_type=password -d client_id=foo -d scope=read -d username=user -d password=test

4. 클라이언트 인증 플로우 (Client Credentials flow)

클라이언트가 직접  자신의 정보를 통해 엑세스 토큰을 발급한다. ( 리프러시 토큰 제공 X ) 

$ curl -F "grant_type=client_credentials" -F "scope=read" "http://foo:bar@localhost:8080/oauth/token"

5. 리프러시 토큰(Refresh  Token)를 통한 엑세스 토큰(Access Token) 재발급  

기존에 저장해둔 리프러시 토큰이  존재할 때 엑세스 토큰 재발급받을 필요가 있을 때 사용한다.

그리고 기존 엑세스 토큰은 만료된다.

$ curl -F "grant_type=refresh_token" -F "scope=read" -F "refresh_token=발급된 Refresh Token" "http://foo:bar@localhost:8080/oauth/token"



엑세스 토큰을 사용하여 API에 접근 테스트

위에서 여러 가지 방법으로 발급된 엑세스 토큰을 사용해서 API를 호출해보자.

$ curl -H "Authorization: Bearer 발급된 AccessToken" "http://localhost:8080/members"
# e.g.
$ curl -H "Authorization: Bearer 05e63e85-9614-446a-8904-aa6cc556bb1b" "http://localhost:8080/members"

json 정보가  확인되면 성공이다. 


다음으로 위 소스에서 설정했던 어노테이션에 대해 한 번  살펴보자.


@EnableResourceServer 

API 서버를 OAuth2  인증받게 만들도록 하며 하는 역할을 한다. 기본 옵션은 모든 API의 모든 요청에 대해 OAuth2 인증을 받도록 한다. 

세부적인 설정을 위해서는 아래와 같이 ResourceServerConfigurerAdapter 클래스를 상속받아서 configure 메서드를  구현해야 한다.

// ...
@EnableResourceServer
@SpringBootApplication
public class DemoApplication extends ResourceServerConfigurerAdapter {
   @Override
   public void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
         .antMatchers("/api/**").authenticated();      
   }
// ...
}

확장을 하지 않고 기본 옵션은 모든 API는 인증이 필요한 형태로 설정된다.

OAuth2 인증을 확인하기 위하여 OAuth2 토큰 스토어 지정해야 하며, 직접 설정을 하지 않았으면 인메모리 형태로 지정된다. ( 위에서는 지정하지 않았으니 인메모리 형태로 된 상태이다. )
위와 같이 OAuth2 서버API 서버가 같은 곳에서  처리되는 형태라면 같은 기본적으로 인메모리 토큰 스토어를 서로 공유하게 된다.

여기 예제에서는 스프링 부트의 기본 설정을 사용해서 모든 API를 인증받도록 할 예정이다. ( 즉 설정을 안 할 예정이다.)

참고로 스프링 부트에서 기본적으로 설정되는 Class의 위치는 아래와 같다. 
org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerConfiguration

@EnableAuthorizationServer

OAuth2 인증서버를  활성화시켜주는 어노테이션이다.

OAuth2 인증을 위한 엑세스 토큰, 리프러시 토큰 발급과 발급된 토큰을 통한 OAuth2 인증 등 핵심기능을  활성화시켜 준다.  

내부에서는 "/oauth/token", "/oauth/authorize" 등 기본적으로 OAuth2에서 사용하는 URI의 접근을 활성화 및 인증 및 내부 예외 처리 기능 등을 가진다. 

세부적인 설정을 위해서는 아래와 같이 AuthorizationServerConfigurerAdapter 클래스를  상속받아서 configure 메서드를  구현해야 한다.

@EnableAuthorizationServer
@SpringBootApplication
public class DemoApplication extends AuthorizationServerConfigurerAdapter {
// ...
   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// OAuth2 인증서버 자체의  보안 정보를 설정하는 부분
   }

   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// Client 에 대한 정보를  설정하는 부분
   }

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// OAuth2 서버가 작동하기 위한 Endpoint에 대한 정보를 설정
   }
   // ...
}

위와 같이 확장할 수 있지만 여기서는 기본적으로 스프링부트에서 기본적으로  설정해주는 형태 그대로 사용할 예정이며 추후 필요한 형태로 확장할 예정이다.

참고로 스프링부트에서 기본적으로 설정되는 Class의 위치는 아래와 같다.
org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration

엑세스 토큰 발급 관련 설명 하는 부분이 생각보다 많이 길어졌다. 설명은 많이 했지만 사실 코드는 얼마 되지 않는다. 그리고 OAuth2 인증 자체가 복잡한 부분이 있기 때문에 테스트 조차도 복잡해진 부분이 생겼기 때문에 계속 보안할 예정이다.

OAuth2 구체적인 스펙에 관심이 있으신 분은 국내에 출시된 OAuth2 책이 존재하니까 한 번 읽어보길 권한다. (내가 쓴 책은 아니다;;) http://www.hanbit.co.kr/store/books/look.php?p_code=E8842152696

5편부터는 스프링 부트에서 지원하는 OAuth기본 설정의 부족한 부분과 확장해야 될 부분 등을  살펴보려고 한다. 


다음 포스팅: 5. OAuth2 서버를 커스터마이징 해보자(TokenStore 편)

이전 포스팅: 3. API 서버 만들기

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 )
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari