https://docs.spring.io/spring-ldap/docs/current/reference/
이 가이드는 애플리케이션을 생성하고 Spring Security LDAP 모듈로 보안을 유지하는 프로세스를 안내합니다.
LDAP란?
LDAP의 주요 용도
LDAP의 특징
LDAP의 장점
LDAP의 단점
LDAP의 활용 사례
Spring Security에 내장된 Java 기반 LDAP 서버로 보호되는 간단한 웹 애플리케이션을 구축합니다. 사용자 집합이 포함된 데이터 파일을 사용하여 LDAP 서버를 로드합니다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'guides'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
Spring에서 REST 엔드포인트는 Spring MVC 컨트롤러입니다. 다음 Spring MVC 컨트롤러(src/main/java/guides/authenticatingldap/HomeController.java)는 간단한 메시지를 반환하여 GET / 요청을 처리합니다.
package guides.authenticatingldap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@GetMapping("/")
public String index() {
return "Welcome to the home page!";
}
}
전체 클래스는 @RestController
로 표시되어 Spring MVC가 컨트롤러를 자동 감지하고(내장된 검색 기능을 사용하여) 필요한 웹 경로를 자동으로 구성할 수 있습니다.
@RestController
는 또한 뷰가 없기 때문에 Spring MVC에게 텍스트를 HTTP 응답 본문에 직접 쓰라고 지시합니다. 대신 페이지를 방문하면 브라우저에 간단한 메시지가 표시됩니다(이 가이드의 초점은 LDAP로 페이지를 보호하는 것이기 때문입니다).
보안되지 않은 웹 애플리케이션 구축
웹 애플리케이션을 보호하기 전에 웹 애플리케이션이 작동하는지 확인해야 합니다. 그렇게 하려면 몇 가지 핵심 Bean을 정의해야 하며, 이는 Application
클래스를 생성하여 수행할 수 있습니다. 다음 목록(src/main/java/guides/authenticatingldap/AuthenticatingLdapApplication.java)은 해당 클래스를 보여줍니다.
package guides.authenticatingldap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AuthenticatingLdapApplication {
public static void main(String[] args) {
SpringApplication.run(AuthenticatingLdapApplication.class, args);
}
}
브라우저를 열고 http://localhost:8080
을 방문하면 다음 일반 텍스트가 표시됩니다.
Spring Security를 구성하려면 먼저 빌드에 몇 가지 추가 종속성을 추가해야 합니다.
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.ldap:spring-ldap-core")
implementation("org.springframework.security:spring-security-ldap")
implementation("com.unboundid:unboundid-ldapsdk")
Gradle의 아티팩트 해결 문제로 인해
spring-tx
를 가져와야 합니다. 그렇지 않으면 Gradle이 작동하지 않는 이전 버전을 가져옵니다.implementation 'org.springframework:spring-tx:6.1.0'
이러한 종속성은 Spring Security와 오픈 소스 LDAP 서버인 UnboundId
를 추가합니다. 이러한 종속성이 적용되면 다음 예제(src/main/java/guides/authenticatingldap/WebSecurityConfig.java)에 표시된 대로 순수 Java를 사용하여 보안 정책을 구성할 수 있습니다.
또한 LDAP 서버가 필요합니다. Spring Boot은 순수 Java로 작성된 내장 서버에 대한 자동 구성을 제공하며, 이 가이드에서 사용됩니다. ldapAuthentication()
메서드는 로그인 폼의 사용자 이름을 {0}
에 삽입하여 uid={0},ou=people,dc=springframework,dc=org
형태로 LDAP 서버에서 검색하도록 설정합니다. 또한 passwordCompare()
메서드는 인코더와 암호 속성의 이름을 구성합니다.
package guides.authenticatingldap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
return http.build();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("userPassword");
}
}
//@Configuration
//public class WebSecurityConfig {
// @Bean
// public SecurityFilterChain configure(HttpSecurity http) throws Exception {
// return http
// .authorizeRequests()
// .anyRequest().authenticated()
// .and()
// .formLogin(Customizer.withDefaults())
// .build();
// }
//}
주석 처리된 코드는 Spring Security 5.7 이전 버전에서 사용되던 코드입니다. Spring Security 5.7부터는 SecurityFilterChain
인터페이스를 사용하여 컨피규레이션해야 하므로 기존 코드는 더 이상 사용되지 않습니다.
주석 처리된 코드와 현재 코드의 차이점은 다음과 같습니다:
인증 방식:
anyRequest().authenticated()
anyRequest().fullyAuthenticated()
formLogin 사용:
formLogin(Customizer.withDefaults())
LDAP 인증 설정:
ldapAuthentication()
에 설정 포함configure(AuthenticationManagerBuilder)
메서드에 설정 분리현재 코드는 SecurityFilterChain
인터페이스를 사용하여 컨피규레이션을 구성하고, anyRequest().fullyAuthenticated()
메서드를 사용하여 모든 요청에 대해 완전한 인증(인증 및 권한 부여)을 요구합니다. 또한, formLogin
설정을 명시적으로 설정하지 않아 Spring Security의 기본 폼 로그인 기능을 사용합니다.
주석 처리된 코드는 anyRequest().authenticated()
메서드를 사용하여 모든 요청에 대해 단순히 인증만 요구합니다. 또한, formLogin(Customizer.withDefaults())
메서드를 사용하여 Spring Security의 기본 폼 로그인 기능을 사용합니다.
따라서, Spring Security 5.7 이후 버전에서는 주석 처리된 코드 대신 현재 코드를 사용하는 것이 좋습니다.
Spring LDAP에서는 application.properties
파일에 세 가지 애플리케이션 속성을 설정해야 합니다.
spring.ldap.embedded.ldif=classpath:test-server.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
spring.ldap.embedded.port=8389
LDAP 서버는 LDIF(LDAP Data Interchange Format) 파일을 사용하여 사용자 데이터를 교환할 수 있습니다. application.properties
내의 spring.ldap.embedded.ldif
속성을 사용하면 Spring Boot가 LDIF 데이터 파일을 가져올 수 있습니다. 이를 통해 데모 데이터를 쉽게 사전 로드할 수 있습니다. 다음 목록(src/main/resources/test-server.ldif에 있음)은 이 예에서 작동하는 LDIF 파일을 보여줍니다.
dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroups
dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people
dn: ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: space cadets
dn: ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: "quoted people"
dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeople
dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: $2a$10$c6bSeWPhg06xB1lvmaWNNe4NROmZiSpYhlocU/98HNr2MhIOiSt36
dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassword
dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Joe Smeth
sn: Smeth
uid: joe
userPassword: joespassword
dn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Mouse, Jerry
sn: Mouse
uid: jerry
userPassword: jerryspassword
dn: cn=slash/guy,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: slash/guy
sn: Slash
uid: slashguy
userPassword: slashguyspassword
dn: cn=quote\"guy,ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: quote\"guy
sn: Quote
uid: quoteguy
userPassword: quoteguyspassword
dn: uid=space cadet,ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Space Cadet
sn: Cadet
uid: space cadet
userPassword: spacecadetspassword
dn: cn=developers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: developers
ou: developer
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: uid=bob,ou=people,dc=springframework,dc=org
dn: cn=managers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: managers
ou: manager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: submanagers
ou: submanager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
LDIF 파일 사용은 프로덕션 시스템의 표준 구성이 아닙니다. 그러나 테스트 목적이나 가이드에는 유용합니다.
http://localhost:8080
사이트에 접속하면 Spring Security에서 제공하는 로그인 페이지로 이동하게 된다.
사용자 이름 ben
과 비밀번호 benspassword
를 입력합니다. 브라우저에 다음 메시지가 표시되어야 합니다.