일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 설정
- redash
- 머신러닝
- 자바
- ec2
- Jenkins
- config
- 클러스터
- SpringBoot
- hive
- EMR
- aws
- spring
- vue
- 레디스
- 젠킨스
- Mac
- fastcampus
- 자동
- java
- Cluster
- 예제
- login
- 로그인
- Zeppelin
- Docker
- 간단
- gradle
- Redis
- Kafka
- Today
- Total
코알못
내 웹페이지에 LDAP을 연동 해보자! 본문
내 웹페이지의 로그인 할 때 ldap 을 연동하여 로그인 기능을 구현할 것이며
ldap 에 정의된 회원만 내 웹페이지 기능을 이용할 수 있도록 한다.
우선 웹페이지와 ldap 연동을 어떻게 하는지 보기 위해 아래 저자가 만든 git 소스를 통해 파악한다.
스타터 코드라 기본적으로 있어야 하는 코드만 넣어 심플하다.
- https://github.com/works-code/embedded-ldap-login
파악을 완료 했다면 위에 코드를 실행 시켜 어플리케이션을 띄워 ldap 서버를 활성화 시킨다.
ldap 관리를 쉽게 하기 위한 툴인 apacheDirectoryStudio를 아래와 같이 설치한뒤 실행시켜 본다.
$ brew install apache-directory-studio
############ 100.0%
==> Installing Cask apache-directory-studio
==> Moving App 'ApacheDirectoryStudio.app' to '/Applications
🍺 apache-directory-studio was successfully installed!
실행한 화면은 아래와 같다.
noAuth 를 눌러 인증 하지 않도록 하며(스타터 코드에 있는대로 하면 인증이 따로 없다)
fetch Base DNs 를 눌러 정상적으로 base dn 을 가져오는지 확인 한뒤 finish 를 눌러 완료한다.
정상적으로 가져온것을 볼 수 있다.
이제 ldap 스타터 코드를 실행시켜 첫 화면 (http://localhost:8080) 을 접속하면 로그인 페이지가 나오며
readme 에 나온 방법대로 corin/admin@1234 입력하여 로그인 하면 완료 문구가 노출 된다.
이제 임베디드 ldap 이 아닌 저번 시간에 직접 구성한 ldap 을 연동 하기로 한다.
만약 구축해둔 LDAP 이 없다면 아래 저자글 참고하여 만든 뒤 실습 진행하면 된다.
이제 apacheDirectoryStudio에서 구축한 ldap 서버에 접속한뒤 스타터 코드에서 만들어둔 AD 대로 다시 만들어본다.
유저 체크 할시 uid 속성을 통해 id를 체크하나 uid 속성을 포함하는 'inetOrgPerson' 클래스가 없기에 추가 해줘야한다.
관련 클래스를 추가하기 위해서는 cosine.schema(종속성), inetorgperson.schema(실제 클래스가 있는 스키마) 스키마 파일을 include 하면 되는데 '/private/etc/openldap/schema' 경로에 해당 스키마가 있다.
아래와 같이 ldap 설정 파일에서 스키마 include 를 정의 한다.
$ sudo vi slapd.conf
include /private/etc/openldap/schema/core.schema
include /private/etc/openldap/schema/cosine.schema
include /private/etc/openldap/schema/inetorgperson.schema
적용을 위해 ldap 을 재기동 한다.
이제 유저를 만드는 방법은 위에 글에서 설명한 방식과 다르므로 아래와 같이 진행한다.
context entry 사용시 dn을 마음대로 정의할 수 있다.
그다음 사용자 클래스를 선택한뒤 'Add' 를 눌러 추가한다.
그럼 의존되어 있는 class 도 자동으로 추가된다.
그 다음 아래와 같이 DN 을 정의한다.
next 를 누르면 해당 클래스에서 필수로 필요로 하는 값이 빨간색으로 표기되어 있다.
아래와 같이 모두 입력하고 추가 속성을 넣기 위해 오른쪽 상단에 + 버튼을 클릭한다.
패스워드 설정을 위해 userPassword 를 검색하여 선택하고 Finish 를 누른다.
원하는 패스워드와 암호방식 선택한뒤 ok를 누른다.
이제 Finish 를 눌러 유저 추가를 완료 한다.
아래와 같이 정상적으로 사용자가 추가 됐음을 볼 수 있다.
이제 기존 임베디드 ldap 사용하던 코드를 수정해보자
build.gradle 파일에서 임베디드 라이브러리를 주석처리 한다.
dependencies {
...
/*implementation 'com.unboundid:unboundid-ldapsdk'*/ // In-Memory Directory Server
...
}
그 다음 ldap 설정 파일인 LdapWebSecurityConfig을 보면 구성 부분은 같게 하여 수정할 부분은 없으며 port 만 구축한 ldap 서버 포트인 389로 수정한다.
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.ldapAuthentication()
...
.url("ldap://localhost:389/dc=corin,dc=com")
...
}
그리고 application.yml 에 정의한 embedded 설정을 모두 지운다. 지울 코드는 아래와 같으며 모두 제거 한다.
spring:
ldap:
embedded:
ldif: classpath:ldap-server.ldif
base-dn: dc=corin,dc=com
port: 8389
이제 어플리케이션을 실행시키고 메인 페이지 'http://localhost:8080' 에 접속한다.
비밀번호 틀릴시 아래와 같이 나온다. (spring security 기본 화면)
정상적으로 입력시 아래와 같이 문구가 정상적으로 나온다.
이제 ldap 서버에서 sn(성), cn(이름), 그룹 등 속성 정보를 가져와서 로그인 완료시 띄워주도록 하는 실습을 진행해본다.
그 전에 아래와 같이 그룹, 유저를 생성한다.
DN | class | attributes |
ou=groups,dc=corin,dc=com | organizationalUnit | ou=groups |
cn=architectures,ou=groups,dc=corin,dc=com | groupOfUniqueNames | cn=architectures ou=architecture uniqueMember=uid=corin,ou=people,dc=corin,dc=com |
cn=developers,ou=groups,dc=corin,dc=com | groupOfUniqueNames | cn=developers ou=developer uniqueMember=uid=corin,ou=people,dc=corin,dc=com uniqueMember=uid=piano,ou=people,dc=corin,dc=com |
ou=people,dc=corin,dc=com | organizationalUnit | ou=people |
uid=corin,ou=people,dc=corin,dc=com | inetOrgPerson | cn=yoolee sn=hong uid=corin userPassword=[원하는 암호로 설정] |
uid=piano,ou=people,dc=corin,dc=com | inetOrgPerson | cn=piano sn=kim uid=piano userPassword=[원하는 암호로 설정] |
모두 생성하면 전체 구조는 아래와 같다.
이제 유저 정보를 추가적으로 셋팅하기 위해서 유저 정보를 매핑하는 클래스(LdapUserDetailsMapper)를 상속받아 커스텀 하게 만들 것이다.
그 전에 유저정보를 담을 새로운 유저 클래스를 생성하고
@Data
public class LdapUser extends LdapUserDetailsImpl {
private String dn;
private String cn;
private String sn;
private String uid;
private List<String> groups;
}
해당 유저가 속한 그룹을 검색하기 위한 ldapContext 를 bean으로 등록한다.
@Slf4j
@Configuration
public class WebConfig {
@Autowired
public LdapInfo ldapInfo;
@Bean
LdapContext ldapContext(){
LdapContext ctx = null;
try{
Hashtable<String, String> environment = new Hashtable<>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapInfo.getUrl());
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, ldapInfo.getManagerdn());
environment.put(Context.SECURITY_CREDENTIALS, ldapInfo.getManagerpwd());
ctx = new InitialLdapContext(environment, null);
log.info("[INFO] ldapContext Connected!");
}catch (NamingException e){
log.error("[ERROR] Create LdapContext Exception {}", e);
}
return ctx;
}
}
추가로 ldap 연결을 위한 properties는 따로 만든 뒤
// ldap.properties
ldap.url=ldap://localhost:389
ldap.basedn=dc=corin,dc=com
ldap.managerdn=cn=Manager,dc=corin,dc=com
ldap.managerpwd=admin@1234
ldap.usersearchPattern=uid={0},ou=people
ldap.groupsearchPattern=(&(objectclass=groupOfUniqueNames)(uniqueMember=uid=%s,ou=people,dc=corin,dc=com))
쉽게 읽어올 수 있는 vo 객체를 만든다.
@Data
@Configuration
@ConfigurationProperties(prefix = "ldap")
@PropertySource(value = "classpath:ldap.properties")
public class LdapInfo {
private String url;
private String basedn;
private String managerdn;
private String managerpwd;
private String usersearchPattern;
private String groupsearchPattern;
}
이제 LdapUserDetailsMapper 클래스를 상속하는 CustomLdapUserMapper 클래스를 만들고
mapUserFromContext 메소드를 오버라이딩 하여 cn, sn 정보를 가져와 LdapUser 객체에 셋팅하고
유저가 속한 그룹의 경우 filter를 통해 ldap에서 조회한 뒤 LdapUser 객체에 셋팅 한다.
@Slf4j
@Configuration
public class CustomLdapUserMapper extends LdapUserDetailsMapper {
@Autowired
public LdapContext ldapContext;
@Autowired
public LdapInfo ldapInfo;
@Override
public LdapUser mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
LdapUserDetailsImpl details = (LdapUserDetailsImpl) super.mapUserFromContext(ctx, username, authorities);
LdapUser customLdapUser = new LdapUser(){{
setCn(ctx.getStringAttribute("cn"));
setDn(details.getDn());
setSn(ctx.getStringAttribute("sn"));
setUid(details.getUsername());
}};
try{
// 유저가 속한 그룹 검색
SearchControls controls = new SearchControls();
controls.setReturningAttributes(new String[] {"cn","ou"});
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String filter = String.format(ldapInfo.getGroupsearchPattern(), details.getUsername());
NamingEnumeration<?> answer = ldapContext.search(ldapInfo.getBasedn(), filter, controls);
List<String> groups = new ArrayList<>();
while(answer.hasMore()) {
SearchResult searchResult = (SearchResult) answer.next();
Attributes attributes = searchResult.getAttributes();
groups.add(StringUtils.isEmpty(attributes.get("ou")) ? "" : attributes.get("cn").toString().split(":")[1]);
}
customLdapUser.setGroups(groups);
}catch (Exception e){
log.error("[ERROR] {}", e);
}
return customLdapUser;
}
}
그 다음 로그인 성공시에만 유저정보를 담은 LdapUser 객체를 세션에 저장하기 위해
login success handler 를 만들고 세션에 유저정보를 담는 코드를 작성한다.
@Slf4j
@Configuration
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
LdapUser ldapUser = (LdapUser) authentication.getPrincipal();
HttpSession session = request.getSession();
session.setAttribute("ldapUser", ldapUser);
response.sendRedirect("/");
}
}
로그인 성공시 커스텀하게 만든 handler를 탈 수 있도록 spring security 설정을 한다.
@EnableWebSecurity
public class LdapWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().anyRequest().fullyAuthenticated()
.and().formLogin().successHandler(new LoginSuccessHandler());
}
...
}
마지막으로 controller 에서 session 정보를 가져와 화면에 뿌린다.
@Slf4j
@RestController
public class TestController {
@GetMapping("/")
public String index(HttpServletRequest request) {
LdapUser ldapInfo = (LdapUser) request.getSession().getAttribute("ldapUser");
return "Hello World "+ ldapInfo.getSn()+ldapInfo.getCn() +" !! [groups] "+ StringUtils.join(ldapInfo.getGroups(),',');
}
}
이제 테스트를 해보자!
로그인을 하면 아래와 같이 정상적으로 성, 이름, 그룹이 나오는것을 볼 수 있다.
최종 코드는 아래 깃을 참고한다.
:: https://github.com/works-code/ldap-login
끝!
'JAVA' 카테고리의 다른 글
[JPA] JPA 정복하기 - 01. JPA 소개 및 프로젝트 환경 설정 (0) | 2024.01.06 |
---|---|
[Lombok] Junit error: cannot find symbol (0) | 2023.02.07 |
서버 모니터링 툴(visualVM) 을 붙여보자! (0) | 2022.02.19 |
5분 안에 구축하는 Swagger (API 규격서) (2) | 2022.02.05 |
5분 안에 구축하는 Nexus (2) | 2022.02.03 |