본문 바로가기
infra/ldap

Spring에서 LDAP 사용하기 (계정 CRUD, 인증, 비밀번호 변경)

by moonsiri 2022. 8. 4.
728x90
반응형

LDAP 디렉터리 서버는 읽기에 최적화된 계층적 데이터 저장소입니다. 일반적으로 계정 인증 및 권한 부여에 필요한 계정 관련 정보를 저장하는 데 사용됩니다.

이전 게시물에서는 AD와 LDAP을 비교하면서 LDAP 구조에 대해 알아봤는데, 이번에는 스프링에서 계정 인증 및 검색하고 디렉터리 서버에서 계정 생성 및 수정하기 위한 Spring LDAP API 사용법을 알아보겠습니다.

 

 

Maven Dependency

<dependency>
    <groupId>org.springframework.ldap</groupId>
    <artifactId>spring-ldap-core</artifactId>
    <version>...</version>
</dependency>

해당 dependency의 버전은 https://search.maven.org/search?q=a:spring-ldap-core 에서 확인 가능합니다.

 

 

LDAP 데이터

계정 인증, 검색, 생성, 수정을 위한 구조는 이전 게시물에서 언급했던 dn 정보 "cn=Tom, ou=people, o=zoho, c=india"를 사용하겠습니다.

[출처] https://www.windows-active-directory.com/active-directory-ldap.html

Attribute Value
objectClass top
objectClass organizationalPerson
objectClass person
cn Tom
distinguishedName CN=Tom,OU=people,O=zoho,C=india
displayName
userPrincipalName tom@moonsiri.tistory.com
userAccountControl 512
sAMAccountName Tom
badPwdCount 0
lockoutTime 0
memberOf ...
... ...

 

 

Spring LDAP API

스프링에서 ldap api를 사용할 수 있는 방법은 두 가지가 있습니다.

 

1안) ContextSource 및 LdapTemplete Bean 정의

ContextSource는 LdapTemplate을 만드는 데 사용됩니다.

@Bean
public LdapContextSource contextSource() {
    LdapContextSource contextSource = new LdapContextSource();

    contextSource.setUrl("ldap://localhost:636");
    contextSource.setBase("ou=people,o=zoho,c=india");
    contextSource.setUserDn("cn=admin,ou=admin,o=zoho,c=india");
    contextSource.setPassword("password");
    
    return contextSource;
}

 

LdapTemplate은 LDAP 항목의 생성 및 수정에 사용됩니다.

@Bean
public LdapTemplate ldapTemplate() {
    return new LdapTemplate(contextSource());
}

 

추가로 Ldap Pool을 설정할 수 있는데 본문에서는 제외하겠습니다.

 

 

2안) Spring boot starter 사용

스프링 부트 환경에서 Spring Boot Starter Data Ldap 종속성을 사용하면 LdapContextSource 및 LdapTemplate을 자동으로 주입할 수 있습니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>

 

위 dependency를 추가한 뒤 application.yml에 ldap 정보를 구성하면 됩니다.

spring:
	ldap:
		url: ldap://localhost:636
		base: ou=people,o=zoho,c=india
		username: cn=admin,ou=admin,o=zoho,c=india
		password: password

 

 


 

 

Spring framework에 LDAP 설정이 완료되었으니 이번엔 LdapTemplate을 사용하여 LDAP 데이터 CRUD 기능을 확인해보겠습니다. (MS AD 기준으로 작성되었으며, OpenLDAP을 사용할 경우 다를 수 있습니다.)

@Resource
private LdapTemplate ldapTemplate;

 

 

계정 생성

public void createLdapUser(String username) {
    Object createUser = ADUserSchemaVO.builder()	// ADUserSchemaVO는 편의를 위해 구현한 객체
      .cn("Tom")
      .displayName("톰")
      .userPrincipalName("Tom@moonsiri.tistory.com")
      .userAccountControl("544")  // 비밀번호가 셋팅되기 전 활성화 상태
      .sAMAccountName("Tom")
    .build();
    
    ldapTemplate.create(createUser);
}

 

 

계정 검색

public ADUserSchemaVO findLdapUser(String username) {
    LdapQuery qry = LdapQueryBuilder.query().where("cn").is(username);
    return ldapTemplate.findOne(qry, ADUserSchemaVO.class);
}

ldapTemplate의 base가 Tom의 dn suffix로 설정되어있기 때문에 cn으로만 Tom을 찾을 수 있습니다.

 

 

계정 수정

 

계정 비밀번호 설정

ldap 수정 기능을 사용하여 계정 비밀번호 설정 방법입니다.

public void setLdapPassword(String username, String password) {
    String userCn = "CN=" + username;

    String newQuotedPassword = "\"" + password + "\"";
    byte[] newUnicodePassword = newQuotedPassword.getBytes(StandardCharsets.UTF_16LE.name());

    ModificationItem[] mods = new ModificationItem[1];
    mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));
    ldapTemplate.modifyAttributes(userCn, mods);
}

 

계정 잠금 해제

이번엔 비밀번호 오류 횟수 초과 정책에 의해 계정이 잠겼을 경우 잠금을 풀기 위해 계정 정보를 수정해보겠습니다. 

public void clearBadPasswordBlock(String username) {
    ADUserSchemaVO adUserVO = findLdapUser(username);
    DirContextOperations context = ldapTemplate.lookupContext(adUserVO.getDn());
    
    // badPwdCount 를 0으로 초기화 -> lockoutTime을 0으로 초기화(순서 중요)
    // context.setAttributeValue("badPwdCount", "0");
    context.setAttributeValue("lockoutTime", "0");
    ldapTemplate.modifyAttributes(context);
}

 

 

계정 삭제

public void deleteLdapUser(String username) {
    ADUserSchemaVO object = ADUserSchemaVO.builder().cn(username).build();
    ldapTemplate.delete(object);
}

 

 

계정 인증

public void ldapAuthenticate(String username, String password) {
    // AD의 경우 유저 엔티티 ObjectClass에 person이 생성됨
    LdapQuery query = query().where("cn").is(username).and("objectClass").is("person");

    try {
        ldapTemplate.authenticate(query, password);
    } catch (Exception e) {
    	int errorCd = getLdapDecErrorCd(e);
        System.out.println(errorCd);
    }
}

 

만약 인증 중에 오류가 발생하면 오류 메시지에 오류코드를 HEX값으로 리턴하는데, 해당 오류코드는 아래 URL표의 hex값과 비교하면 됩니다.

The exception is [LDAP: error code 49 - 80090308: LdapErr: DSID-0Cxxxxxx, comment: AcceptSecurityContext error, data <HEX>, vece ].

https://ldapwiki.com/wiki/Common%20Active%20Directory%20Bind%20Errors

 

Ldapwiki: Common Active Directory Bind Errors

Overview# Here are the LDAP Result Codes you might see along with LDAP Result Code 49 which would cause Authentication Failures When you see an entry similar to: "The exception is [LDAP: error code 49 - 80090308: LdapErr: DSID-0Cxxxxxx, comment: AcceptSecu

ldapwiki.com

public int getLdapDecErrorCd(Exception exception) {
    // Exception에서 message 조회
    String message = exception.getMessage();

    // LDAP 에러 메세지에서 코드를 파싱하는 정규식으로 hex 코드 조회
    Matcher m = Pattern.compile(".*data\\s([0-9a-f]{3,4}).*").matcher(message);
    if (m.matches()) {
        return Integer.parseInt(m.group(1), 16);  // 에러코드를 dec로 파싱
    } else {
    	return -1;
    }
}

 

 

[Reference]

https://www.baeldung.com/spring-ldap

https://shanepark.tistory.com/303

https://www.python2.net/questions-898548.htm

728x90
반응형

댓글