SpringSecurity OAuth2 이용해 인증서버를 구현해서 운영하려고 보면 여러가지 커스터마이징이 필요하게 된다. 중에서 인증정보를  LDAP 서버에서 받아오는 방식에 대해서 다루어 보려고 한다.

일반적으로는 인증정보는 DB에 저장되어 있고  가져온다. 하지만 동일한 인증정보를 다른 서비스에서 가져가야 할때, 해당 서비스와 DB를 연동하기 어려울 때가 있다. 레드마인이나 이메일 등 만들어진 서비스의 경우 그러하다. 이를 해결하기 위해 대부분의 서비스들은 LDAP 인증을 지원한다. 인증정보가 담긴 곳 앞에 LDAP 서버를 두고 모든 서비스가 LDAP 서버와 연동이 되면 일괄적인 인증정보 관리가 되기 때문에 편리해 진다.

LDAP은 엄밀히 이야기해서 프로토콜이다. 이 프로토콜을 지원하는 서버들이 몇가지 있지만 일반적으로 사용되는건 OpenLDAP 이다. 이 곳에서 OpenLDAP 설치는 별도로 다루지 않는다.

OpenLDAP 이란?



Spring Boot 에 OAuth2 인증 서버 만들기

우선 간단하게 SpringSecurity OAuth2 인증서버를 구성하고, 여기에 LDAP 서버를 연동하는 방법을 설명한다. LDAP 연동을 위한 OAuth2 구현이기 때문에 간략하게 다룬다. 이대로 OAuth2 구현한다면 매우 위험할수 있으므로 OAuth2 구현은 다른곳을 참조하자.

최종적으로 사용된 소스는 이 곳에서 확인 가능하다.


1. 라이브러리 버전 선택

Spring Boot 2 버전 부터 SpringSecurity 를 이용하는 방식이 바뀌었다.


OAuth 2.0 Supports

Functionality from the Spring Security OAuth project is being migrated to core Spring Security. OAuth 2.0 client support has already been added and additional features will be migrated in due course.

If you depend on Spring Security OAuth features that have not yet been migrated you will need to add org.springframework.security.oauth:spring-security-oauth2 and configure things manually. If you only need OAuth 2.0 client support you can use the auto-configuration provided by Spring Boot 2.0. We’re also continuing to support Spring Boot 1.5 so older applications can continue to use that until an upgrade path is provided.

인터넷 상의 대부분의 예제가 SpringBoot 1.x 버전을 대상으로 다루고 있는데, @EnableOAuth2Sso 등의 어노테이션을 이용한다. 하지만 SpringBoot2 에서는 기본적으로 EnableOAuth2Sso 어노테이션을 지원하지 않는다. 이를 사용하려면 구버전과 호환되는 SpringSecurity 버전을 사용해야 한다.

이런 방식을 사용하지 않고 @EnableAuthorizationServer, @EnableResourceServer 어노테이션을 사용하는 SpringBoot2 에 맞춰진 방식으로 구현하였다.


2. Maven Dependencies


  org.springframework.boot
  spring-boot-starter-parent
  2.0.1.RELEASE
  


.
.
.

  
      org.springframework.cloud
      spring-cloud-security
      RELEASE
  
  

SpringBoot 2.0.1 버전을 사용하였다.
SpringSecurity를 추가하기 위해 spring-cloud-security 디펜던시를 추가하였다. SpringBoot 1.x 버전과 호환되지 않으므로 주의하여야 한다.


3. OAuth2 구현

OAuth2 구현에 필요한 클래스들의 기능을 간략하게 정리해보았다. 실제 소스는 깃헙링크를 참조하면 된다.

[AuthorizationServerConfig]
인증 처리를 담당한다.

[ResourceServerConfig]
권한처리를 담당한다.

[SecurityConfig]
인증정보를 가져오는 곳을 설정한다.
이곳에서는 DefaultUserDetailsService 의 구현에 따라 인증정보를 가져온다.

[DefaultUserDetailsService]
테스트 용도로 인증정보를 넣어두었다.

정상적으로 동작을 한다면 아래와 같은 요청과 응답이 온다.

[Request]
curl -X POST \
 http://localhost:8080/oauth/token \
 -u test-client:test-secret \
 -H 'content-type: multipart/form-data' \
 -F username=gaecheok@test.com \
 -F password=1234 \
 -F grant_type=password

[Response]
{
   "access_token": "e51b8482-ffeb-437b-9fa4-765833c44661",
   "token_type": "bearer",
   "refresh_token": "de1b49c5-1213-4247-8709-ef1fc602596b",
   "expires_in": 3599,
   "scope": "read write trust"
}

OAuth2 구현은 LDAP 연동을 위해 간략하게 적용하였다. 다시한번 강조하지만 테스트 용도 외의 사용은 위험하니 주의하여야 한다. OAuth2 에 대한 구체적인 구현은 다른곳을 참조하여서 작성하도록 하자.



SpringSecurity의 인증정보를 LDAP으로 연동하기

1. Maven Dependencies 추가

SpringSecurity 의 LDAP 라이브러리를 추가한다.

   
       org.springframework.security
       spring-security-ldap
       5.0.3.RELEASE
   


2. LDAP 권한 처리 클래스 생성

[LdapAuthoritiesPopulator]
권한을 강제로 할당하도록 구현하였다. 이렇게 처리하면 사용자에게 ROLE_ADMIN 이라는 권한이 부여된다.
실제 서비스에서는 DB, 혹은 LDAP 서비스 내에 있는 사용자에 맞는 권한 정보를 가져와서 authorities 객체에 권한정보를 추가해 주어야 한다.

public class LdapAuthoritiesPopulator implements org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator {
   private DefaultUserDetailsService userService;

   public LdapAuthoritiesPopulator(DefaultUserDetailsService userService) {
       this.userService = userService;
   }

   @Override
   public Collection getGrantedAuthorities(
           DirContextOperations userData, String username) {

       Collection authorities = new HashSet<>();
       authorities.add(new SimpleGrantedAuthority("ADMIN"));

       //userService 와 연계된 DB 등 에서 권한정보를 가져다가 이곳에서 리턴해주면 됨.
       //authorities.addAll(userService.getAuthorities(username));

       return authorities;
   }
}


3. LDAP 서버를 바라보도록 설정

[SecurityConfig]
기존에는 DefaultUserDetailsService 에서 인증정보를 가져왔지만, auth.ldapAuthentication()을 이용하여 인증정보를 LDAP 에서 가져오도록 설정한다.
권한정보를 가져올수 있도록 LdapAuthoritiesPopulator를 정의한다.

@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
   //auth.userDetailsService(userDetailsService);

   auth.ldapAuthentication()
           .ldapAuthoritiesPopulator(new LdapAuthoritiesPopulator(userDetailsService))
           .userDnPatterns("uid={0}")
           .contextSource()
           .url("ldap://192.168.1.1:389/dc=n,dc=co.kr")
           .managerDn("cn=admin,dc=n,dc=co.kr")
           .managerPassword("secret");
}

LDAP 접속 정보에 맞게 입력해준다.
일반적으로는 URL, ManagerDn, ManagerPassword 가 필요하고, 경우에 따라 userDN 등이 필요하다.
OpenLDAP 을 설치하였다면 위와 비슷한 내용으로 구성이 된다.



4. 연동확인

auth.ldapAuthentication() 구현이 완료되면 이제 사용자가 접속할 때, 사용자의 아이디-비번을 LDAP 쪽에 전달해 맞는지 여부를 가져오게 된다.
인증정보가 맞다면 권한정보를 가져오기 위해 userDetailsService 를 사용하고, 틀리다면 401 이 리턴된다.


여기까지 LDAP 연동을 살펴보았다. 


실제 운영을 하게 된다면 이 연동 외에도 다양한 요구사항이 있을 수 있다. 

겪었던 요구사항 중 한가지가 OpenLDAP 과 별도의 인증DB 에서 인증정보를 가져오는 것이었다. OpenLDAP이 바라보는 저장소의 인증은 글로벌하게 사용하고, 별도의 인증DB에서는 LDAP 쪽 과는 별개의 계정을 등록해서 사용하기 위함이었다.

user1 이라는 사용자가 로그인 했을때, DB에서 확인하고 없으면 LDAP 에서 확인하도록 구현이 필요했다.

이때는..

userDetailsService 에서 디비에서 인증정보를 가져오도록 구현 한 후,
위에 SecurityConfig 에서    auth.ldapAuthentication() 과 auth.userDetailsService(userDetailsService) 이것을 2개다 설정하면 된다.
이렇게 설정한다면 DB에서 먼저 사용자 인증을 확인하고 있으면 그대로 권한 정보를 가져오고, 없다면 LDAP에서 동일하게 인증과정을 진행을 한다.


Posted by panpid

댓글을 달아 주세요