[spring security + uaa] 로그인용 OAuth 서버 사용하기

목표

로그인 전용 서버를 따로 두고, 여러 어플리케이션에서 로그인을 처리하지 않고, 로그인 전용 서버에 역할을 위임해서 처리하고 싶다. 이 글에서는 OAuth를 사용하여 로그인 전용 서버로 사용했다.





App 이라는 웹 어플리케이션에서 로그인 서버의 인증을 사용하는 케이스의 sequence diagram 이다. 인증 방법은 authorization_code 방법이다.

App에서는 사용자의 정보를 가지고 있지 않고, 로그인 서버에서 사용자 정보를 가져온 후, 세션에 저장한다.

코드 관점에서 보면 App이 Spring Security를 사용한다면, SecurityContext에 외부에서 가져온 데이터를 채워 넣어야 한다.

방법

CloudFoundry의 User Account and Authentication Service(UAA) 에 이러한 서비스를 제공하는 라이브러리가 있다.

<dependency>
    <groupId>org.cloudfoundry.identity</groupId>
    <artifactId>cloudfoundry-identity-common</artifactId>
    <version>1.4.3</version>
</dependency>

이 라이브러리 안에는 ClientAuthenticationFilter가 있는데, UsernamePasswordAuthenticationFilter 대신 사용하면 된다.

샘플용 소스를 하나 만들어서 github에 올렸다. https://github.com/jekalmin/samples 에서 uaa 폴더 안에 프로젝트가 두개 있는데, 아래처럼 둘 다 받아서 서버에 올리면 된다.

여기서는 oauth_consumer가 위에서 설명한 App이고, oauth_provider가 Login Server이다.

서버를 실행하고, 위의 Authentication Sequence를 보면서 테스트해보자.

먼저 http://localhost:8080/oauth_consumer를 호출하면, Authentication Sequence의 1,2,3,4 번에 해당하는 작업을 수행한다. 그래서 아래 이미지와 같이 4번에 해당하는 로그인 페이지에 오게 되었다.

min / min 계정으로 로그인을 해보면 아래 이미지와 같이 권한 승인 페이지로 가게 되는데, 이 부분은 Authentication Sequence의 6번에 해당하는 권한 승인 페이지이다.

승인을 하게 되면, Authentication Sequence의 7번부터 15번까지 실행되고 아래와 같이 http://localhost:8080/oauth_consumer 에 해당하는 페이지가 출력된다.

User Account and Authentication Service(UAA)

위의 예제에서는 App 부분인 oauth_consumer에만 UAA의 라이브러리를 사용했고, 로그인 서버부분인 oauth_provider는 예전에 포스팅했던 http://jekalmin.tistory.com/entry/spring-bootoauth-%EC%84%B8%ED%8C%85-%ED%85%8C%EC%8A%A4%ED%8A%B8 글의 코드와 같다.

oauth_consumer에서 UAA의 ClientAuthenticationFilter가 Authentication Sequence의 핵심인 2,10,12,14 번에 해당하는 부분들을 담당하고 있다.

UAA에서는 클라이언트 사이드 뿐만 아니라 서버사이드도 많은 기능을 제공하는데(revoke token, RemoteTokenStore, etc..), 사실 서버쪽의 기능들이 더 핵심인 것 같다.

p.s.) ClientAuthenticationFilter에서 redirect와 /oauth/token을 요청하는 부분은 https://github.com/dsyer/uaa/blob/feature/bootify/common/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSource.java 의 99번째 줄인 restTemplate에 프록시로 걸려있다.

Sample Code

https://github.com/jekalmin/samples

Reference