Ubuntu server 20.04에 Redmine을 설치하는 법에 대해 알아봅니다.

 

 

 

0. 사전 준비.

 

우분투 서버를 미리 설치해 둡니다. 우분투 서버에 접속 해 apt-get update를 진행해 주세요.

 

 

 

1. MariaDB 설치 및 Database 구성.

 

Redmine은 MariaDB를 사용합니다. Redmine 설치에 앞서 MariaDB를 설치해 줍시다.

 

$ sudo apt-get install mariadb-server

 

MariaDB가 설치되면 이제 Database를 생성해줍시다.

 

$ sudo mysql -u root -p

> CREATE DATABASE redmine CHARACTER SET utf8mb4;

 

 

DB 생성 후 redmine DB에 유저를 생성한 뒤 권한을 부여해 줍니다.

 

> GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost' IDENTIFIED BY 'P@SSWORD';

> FLUSH PRIVILEGES;

 

 

생성한 유저로 로그인해서 DB가 제대로 생성되었는지 확인합니다.

 

$ mysql -u redmine -p

> SHOW DATABASES;

 

 

 

 

2, Apache 설치.

 

Redmine을 서비스하기 위해 필요한 제품을 설치합니다.

 

$ sudo apt-get install apache2 libapache2-mod-passenger

 

 

 

3. Redmine 설치.

 

이제 Redmine을 설치합니다.

 

$ sudo apt-get install redmine redmine-mysql

 

설치를 진행하다 보면 "Configuring redmine" 화면이 나타납니다. 설정을 진행해 줍시다.

 

OK를 선택합니다.

 

Yes를 선택합니다.

 

 

관리자 암호를 입력해줍니다.

 

 

다시 한번 입력하도록 합니다. 이후 설치가 완료되면 다음 명령어를 통해 bundler를 설치합니다.

 

$ sudo gem update

$ sudo gem install bundler

 

 

설치 완료 후 Apache passenger 모듈의 설정 파일을 다음과 같이 수정합니다.

 

$ sudo vi /etc/apache2/mods-available/passenger.conf

 

<IfModule mod_passenger.c>
  PassengerDefaultUser www-data
  PassengerRoot /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini
  PassengerDefaultRuby /usr/bin/ruby
</IfModule>

 

 

이제 www에 Redmine의 심볼릭 링크를 생성해 줍니다.

 

$ sudo ln -s /usr/share/redmine/public /var/www/html/redmine

 

 

 

4. Apache2에 Redmine 구성파일 생성.

 

/etc/apache2/sites-available 폴더에 Redmine.conf 파일을 생성 한 뒤 다음과 같이 작성합니다.

 

$ sudo vi /etc/apache2/sites-available/redmine.conf

<VirtualHost *:80>
  ServerAdmin admin@example.com
  DocumentRoot /var/www/html/redmine
  ServerName projects.example.com
  ServerAlias www.projects.example.com
  <Directory /var/www/html/redmine>
    RailsBaseURI /redmine
    PassengerResolveSymlinksInDocumentRoot on
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

 

 

만약 도메인 이름을 아직 정하지 못했거나 없다면 위의 방식 대신 아래의 방식을 사용해 IP주소를 통해 Redmine에 액세스 할 수 있습니다.

 

위와는 다른 000-default.conf 파일을 수정합니다.

 

$ sudo vi /etc/apache2/sites-available/000-default.conf

 

<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/html/redmine
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  <Directory /var/www/html/redmine>
    RailsBaseURI /redmine
    PassengerResolveSymlinksInDocumentRoot on
  </Directory>
</VirtualHost>

 

 

이제 Apache의 www-data 사용자가 액세스 할 수 있도록 Gemfile.lock을 생성하고 권한을 부여해 줍니다.

 

$ sudo touch /usr/share/redmine/Gemfile.lock

$ sudo chown www-data:www-data /usr/share/redmine/Gemfile.lock

 

구성 파일대로 Redmine을 활성화합니다.

 

$ sudo service apache2 restart

 

이제 호스트나 IP주소를 입력해 웹사이트로 이동해 봅시다.

 

 

정상적으로 Redmine이 활성화된 것을 확인할 수 있습니다. 이제 Redmine에서 프로젝트에 대한 설정을 한 뒤 사용하시면 됩니다.

 

기본 Admin계정 및 암호는 admin/admin입니다.

 

 

 

 

 

 

Python의 BeautifulSoup를 사용해 만든 크롤러를 개선해봅니다.

 

 

1. 사전 준비

 

본 연습 프로젝트는 레드 마인의 이슈를 크롤링 해오는 것을 목표로 합니다.

크롤링을 목표로 하기 때문에 레드 마인의 API는 사용하지 않습니다.

 

이전 코드의 내용은 여기에서 확인해 주세요.

 

 

 

2. 시그널 처리기 추가하기.

 

현재의 소스는 한번 크롤링해오고 종료되는 방식입니다. 이제 이 크롤러가 주기적으로 동작하도록 수정해 봅시다.

 

소스코드의 내용을 다음과 같이 수정해 주세요.

 

import requests
import datetime
import time
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__main_url = 'https://src.smoh.kr/'
        self.__login_url = 'https://src.smoh.kr/login'
        self.__search_url = 'https://src.smoh.kr/projects/crawlerissues/issues?set_filter=1'
        self.__login_data = {
            'username': 'jd_user',
            'password': 'P@ssword'
        }
        self.__issues = {}
        self.__stop = False;
        return

    def main(self):
        while not self.__stop:
            with requests.Session() as s:
                main_req = s.get(self.__main_url)
                html = main_req.text
                obj = BeautifulSoup(html, 'html.parser')
                auth_token = obj.find('input', {'name': 'authenticity_token'})['value']
                self.__login_data = {**self.__login_data, **{'authenticity_token': auth_token}}

                login_req = s.post(self.__login_url, self.__login_data)
                if login_req.status_code == 200:
                    search_req = s.get(self.__search_url)
                    search_obj = BeautifulSoup(search_req.text, 'html.parser')
                    thead = search_obj.select('div.autoscroll > table > thead > tr > th')
                    tbody = search_obj.select('div.autoscroll > table > tbody > tr > td')
                    issue_obj = {}
                    issue_list = []
                    for i in range(1, len(tbody) +1):
                        if(i % len(thead) != 0):
                            issue_obj[thead[i % len(thead)].text] = tbody[i].text
                        else:
                            issue_list.append(issue_obj)
                            issue_obj = {}
                    self.__issues = {
                        'execution_dttm': str(datetime.datetime.now().isoformat()),
                        'issues': issue_list
                    }
                else:
                    print('Failed to login(Code: %d)' %(req.status_code))
                print(self.__issues)
                time.sleep(5)
        return

crawler = Crawler()
crawler.main()

 

별로 변경된 내용은 없습니다. init에 __stop을 추가해주고 해당 값이 False이면 크롤링 작업을 5초마다 반복합니다.

 

중지하고 싶을 땐 Ctrl + C를 눌러서 중지합니다.

 

 

종료 방법도 Ctrl + C 밖에 없을뿐더러 종료 시 위와 같은 이상한 에러가 발생합니다. 

 

시그널을 처리할 수 있도록 수정하여 깔끔하게 종료될 수 있도록 수정해 봅니다.

 

import requests
import datetime
import time
import signal
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__main_url = 'https://src.smoh.kr/'
        self.__login_url = 'https://src.smoh.kr/login'
        self.__search_url = 'https://src.smoh.kr/projects/crawlerissues/issues?set_filter=1'
        self.__login_data = {
            'username': 'jd_user',
            'password': 'P@ssword'
        }
        self.__issues = {}
        self.__stop = False;

        signal.signal(signal.SIGINT, self.stop)
        signal.signal(signal.SIGTERM, self.stop)
        return

    def stop(self, signum, frame):
        self.__stop = True
        return

    def main(self):
        while not self.__stop:
            with requests.Session() as s:
                main_req = s.get(self.__main_url)
                html = main_req.text
                obj = BeautifulSoup(html, 'html.parser')
                auth_token = obj.find('input', {'name': 'authenticity_token'})['value']
                self.__login_data = {**self.__login_data, **{'authenticity_token': auth_token}}

                login_req = s.post(self.__login_url, self.__login_data)
                if login_req.status_code == 200:
                    search_req = s.get(self.__search_url)
                    search_obj = BeautifulSoup(search_req.text, 'html.parser')
                    thead = search_obj.select('div.autoscroll > table > thead > tr > th')
                    tbody = search_obj.select('div.autoscroll > table > tbody > tr > td')
                    issue_obj = {}
                    issue_list = []
                    for i in range(1, len(tbody) +1):
                        if(i % len(thead) != 0):
                            issue_obj[thead[i % len(thead)].text] = tbody[i].text
                        else:
                            issue_list.append(issue_obj)
                            issue_obj = {}
                    self.__issues = {
                        'execution_dttm': str(datetime.datetime.now().isoformat()),
                        'issues': issue_list
                    }
                else:
                    print('Failed to login(Code: %d)' %(req.status_code))
                print(self.__issues)
                time.sleep(5)
        return

crawler = Crawler()
crawler.main()

 

init에 signal을 추가했으며 __stop를 다루는 stop함수를 추가했습니다. 이제 Ctrl + C로 종료를 해도 에러 없이 종료되는 모습을 확인할 수 있습니다.

 

 

 

3. Logger 추가하기.

 

크롤러의 작업 내용을 기록하는 로거를 만들어 적용해 보도록 하겠습니다.

 

logger.py 파일을 새로 만든 후 다음과 같이 코딩해 주세요.

 

import logging
import time
import json
import datetime
import os

class Logger:
    def __init__(self, name, log_file):
        self.name = name
        self.log_file = log_file
        logging.basicConfig(level = logging.INFO, format = '%(message)s')
        self.logger = logging.getLogger(self.name)
        self.log_hadler = logging.FileHandler(self.log_file)
        self.logger.addHandler(self.log_hadler)
        return
    
    def log(self, event, value):
        log_obj = {
            'component': self.name,
            'dttm': str(datetime.datetime.now().isoformat()),
            'log': {
                'event': event,
                'desc': value
            }
        }
        self.logger.info(json.dumps(log_obj))
        return

 

로거는 컴포넌트 이름과 파일 경로를 받아와서 로그 파일을 남깁니다. 남겨지는 로그의 구조는 log_obj에 정의되어 있습니다.

 

현재는 info레벨의 로그만 남기도록 되어 있으므로 필요에 따라 수정해서 남기면 됩니다.

 

이제 로거를 사용해 보도록 합니다. crawler.py 파일을 다음과 같이 수정합니다.

 

import requests
import datetime
import time
import signal
import os
from bs4 import BeautifulSoup
from logger import Logger

class Crawler:
    def __init__(self):
        self.__main_url = 'https://src.smoh.kr/'
        self.__login_url = 'https://src.smoh.kr/login'
        self.__search_url = 'https://src.smoh.kr/projects/crawlerissues/issues?set_filter=1'
        self.__login_data = {
            'username': 'jd_user',
            'password': 'P@ssword'
        }
        self.__issues = {}
        self.__stop = False;
        self.__log_file = 'D:\\crawler.log'

        self.logger = Logger('crawler', self.__log_file)

        signal.signal(signal.SIGINT, self.stop)
        signal.signal(signal.SIGTERM, self.stop)
        return

    def stop(self, signum, frame):
        self.__stop = True
        return

    def main(self):
        self.logger.log('start', { 'pid': os.getpid() })
        while not self.__stop:
            with requests.Session() as s:
                main_req = s.get(self.__main_url)
                html = main_req.text
                obj = BeautifulSoup(html, 'html.parser')
                auth_token = obj.find('input', {'name': 'authenticity_token'})['value']
                self.__login_data = {**self.__login_data, **{'authenticity_token': auth_token}}

                login_req = s.post(self.__login_url, self.__login_data)
                if login_req.status_code == 200:
                    search_req = s.get(self.__search_url)
                    search_obj = BeautifulSoup(search_req.text, 'html.parser')
                    thead = search_obj.select('div.autoscroll > table > thead > tr > th')
                    tbody = search_obj.select('div.autoscroll > table > tbody > tr > td')
                    issue_obj = {}
                    issue_list = []
                    for i in range(1, len(tbody) +1):
                        if(i % len(thead) != 0):
                            issue_obj[thead[i % len(thead)].text] = tbody[i].text
                        else:
                            issue_list.append(issue_obj)
                            issue_obj = {}
                    self.__issues = {
                        'execution_dttm': str(datetime.datetime.now().isoformat()),
                        'issues': issue_list
                    }
                else:
                    self.logger.log('login_fail', { 
                        'pid': os.getpid(), 
                        'error_code':  req.status_code
                    })
                self.logger.log('get_issues', { 
                    'pid': os.getpid(),
                    'count': len(issue_list)
                })
                print(self.__issues)
                time.sleep(5)
        self.logger.log('stop', { 'pid': os.getpid() })
        return

crawler = Crawler()
crawler.main()

 

이제 코드를 실행시키면 로그 파일 경로에 다음과 같이 정상적으로 로그가 남는 것을 확인할 수 있습니다.

 

{"component": "crawler", "dttm": "2020-02-03T10:45:16.780440", "log": {"event": "start", "desc": {"pid": 30568}}}
{"component": "crawler", "dttm": "2020-02-03T10:45:17.729584", "log": {"event": "get_issues", "desc": {"pid": 30568, "count": 3}}}
{"component": "crawler", "dttm": "2020-02-03T10:45:23.683570", "log": {"event": "get_issues", "desc": {"pid": 30568, "count": 3}}}
{"component": "crawler", "dttm": "2020-02-03T10:45:28.705034", "log": {"event": "stop", "desc": {"pid": 30568}}}

 

 

 

 

 

 

 

 

 

Python의 BeautifulSoup를 사용해 크롤러를 만들어 봅니다.

 

 

 

1. 사전 준비

 

본 연습 프로젝트는 레드마인의 이슈를 크롤링 해오는것을 목표로 합니다.

 

레드마인 준비는 별도로 설명하지 않습니다. 

 

** 언제까지 올라가 있을지는 장담할 수 없으나 본문에 제 레드마인 조회용 계정을 사용하셔도 됩니다.

 

별도의 파이썬 설치방법은 설명하지 않습니다. 다만 원활한 개발을 위해 VirtualEnv만 설명합니다.

 

파이썬을 설치하고 VSCode로 프로젝트 폴더를 엽니다.

 

Ctlr + `를 눌러 터미널 창을 열고 다음 명령어로 VirtualEvn를 설치합니다.

 

> pip install virtualenv

 

 

이제 다음 명령어를 통해 VirtualEnv를 활성화시킵니다.

 

> virtualenv venv

 

 

다음과 같이 프로젝트 폴더에 venv 폴더가 생성된 것을 확인할 수 있습니다.

 

 

다른 폴더의 이름을 원하시면 venv대신 다른 이름을 지정하시면 됩니다.

 

이제 virtualenv를 활성화시킵시다. ".\venv\scripts\activate" 파일을 실행시키면 됩니다.

 

>.\venv\scripts\activate

 

 

이제 터미널 앞에 가상 환경으로 정의한 (venv)가 나타난 것을 확인할 수 있습니다.

 

작업을 종료하고 싶으시면 ".\venv\scripts\deactivate"를 입력하시면 됩니다.

 

 

 

2. BeautifulSoup 이용하기

 

BeautifulSoup는 Python 패키지중 하나로 HTML 태그를 Python 객체로 만들어줍니다.

 

문서는 다음과 같이 설명하고 있습니다.

 

"Beautiful Soup transforms a complex HTML document into a complex tree of Python objects."
"BeautifulSoup는 HTML document안에 있는 수많은 HTML 태그들을 사용하기 편한 Python 객체 형태로 만들어 줍니다."

 

이 외에도 파싱을 돕는 다양한 내장 함수가 있어 편하게 원하는 데이터를 추출해 올 수 있습니다.

 

이제 터미널에서 다음 명령어를 실행해 beautisulsoup를 설치합시다.

 

> pip install beautifulsoup4 

 

 

이 외에도 requests라는 패키지가 필요합니다. 이 패키지는 입력한 URL에 대해 반환받는 HTML을 가져오는 역할을 합니다. 다음 명령어를 실행해 requests를 설치합니다.

 

>pip install requests

 

 

이제 이 패키지들을 사용해 보겠습니다. crawler.py 파일을 만든 뒤 다음과 같이 코딩합니다.

 

import requests
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__url = 'https://src.smoh.kr/projects/crawlerissues/issues'
        return

    def main(self):
        response = requests.get(self.__url)
        if response.status_code == 200:
            obj = BeautifulSoup(response.content, 'html.parser')
            print(obj.html)
        else:
            print('Failed(Code: %d)' %(response.status_code))
        return

crawler = Crawler()
crawler.main()

 

지정한 URL로부터 응답을 받고 코드가 200 성공인 경우 내용을 BeautifulSoup을 이용해 객체로 생성한 뒤 html을 출력하는 코드입니다.

 

HTML결괏값이 제대로 보이시나요?

 

 

 

3. 이슈 크롤링하기

 

이 URL 정보는 CrawlerIssues 프로젝트에 생성된 이슈를 보여주는 URL입니다만 우리가 원하는 이슈 정보를 보려고 하기엔 권한이 부족합니다. HTML 결과를 살펴보면 로그인을 하는 화면임을 확인할 수 있습니다.

 

실제 URL로 들어가서 개발자 도구(F12)를 연 뒤 Elements 탭에서 확인해 보겠습니다.

 

 

이제 우리는 "username"에 유저 ID를, "password"에 암호를 넣고 "login" 액션을 통해 로그인을 진행하는 것을 확인했습니다.

 

저기에 유저 정보를 넣어 로그인을 해봅시다. 크롤러가 로그인 정보를 갖기 위해서 세션을 사용합니다. 다음과 같이 코드를 수정합니다.

 

import requests
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__login_url = 'https://src.smoh.kr/login'
        self.__seach_url = 'https://src.smoh.kr/projects/crawlerissues/issues'
        self.__login_data = {
            'username': 'jd_user',
            'password': 'P@ssword'
        }
        return

    def main(self):
        with requests.Session() as s:
            req = s.post(self.__login_url, self.__login_data)
            if req.status_code == 200:
                html = s.get(self.__seach_url)
                print(html)
            else:
                print('Failed to login(Code: %d)' %(req.status_code))
        return

crawler = Crawler()
crawler.main()

 

** 크롤링할 레드마인이 없으신 분은 저 계정을 사용하시면 됩니다만 사전 공지 없이 변경될 수 있음을 알려드립니다.

 

결과가 어떻게 나오나요? "Failed to login(Code: 422)"와 함께 로그인이 제대로 되지 않을 겁니다. 원인이 무엇인지 postman을 통해 직접 post 요청을 날리고 메시지를 확인해 봅시다.

 

 

"Invalid form authenticity token"라는 에러와 함께 로그인에 실패하네요. 혹시 위의 HTML Elements에서 authenticity token을 보셨나요? 

 

 

해당 값은 보안을 위해 발급된 토큰으로 매 로그인 시마다 다른 값을 요구합니다. 따라서 크롤러에서 이 값을 사용하기 위해 코드를 다음과 같이 수정합시다.

 

import requests
import datetime
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__main_url = 'https://src.smoh.kr/'
        self.__login_url = 'https://src.smoh.kr/login'
        self.__search_url = 'https://src.smoh.kr/projects/crawlerissues/issues?set_filter=1'
        self.__login_data = {
            'username': 'jd_user',
            'password': 'P@ssword'
        }
        self.__issues = {}
        return

    def main(self):
        with requests.Session() as s:
            main_req = s.get(self.__main_url)
            html = main_req.text
            obj = BeautifulSoup(html, 'html.parser')
            auth_token = obj.find('input', {'name': 'authenticity_token'})['value']
            self.__login_data = {**self.__login_data, **{'authenticity_token': auth_token}}

            login_req = s.post(self.__login_url, self.__login_data)
            if login_req.status_code == 200:
                search_req = s.get(self.__search_url)
                search_obj = BeautifulSoup(search_req.text, 'html.parser')
                thead = search_obj.select('div.autoscroll > table > thead > tr > th')
                tbody = search_obj.select('div.autoscroll > table > tbody > tr > td')
                issue_obj = {}
                issue_list = []
                for i in range(1, len(tbody) +1):
                    if(i % len(thead) != 0):
                        issue_obj[thead[i % len(thead)].text] = tbody[i].text
                    else:
                        issue_list.append(issue_obj)
                        issue_obj = {}
                self.__issues = {
                    'execution_dttm': str(datetime.datetime.now().isoformat()),
                    'issues': issue_list
                }
            else:
                print('Failed to login(Code: %d)' %(req.status_code))
            print(self.__issues)
        return

crawler = Crawler()
crawler.main()

 

차근차근 코드를 살펴봅시다.

 

우선 url이 세 개로 늘었습니다. auto_token을 받아오는 메인 페이지, 로그인을 요청하는 페이지, 이슈를 조회하는 페이지가 있습니다. 로그인 시 사용하는 객체는 __login_data에 저장해 두었으며 __issues는 조회한 이슈를 정리하여 담아두기 위한 객체입니다.

 

실제 작업의 워크 플로우를 봅시다.

 

먼저 세션을 생성하고 세션 내에서 auth_token을 구해옵니다. 구해온 토큰은 __login_data에 저장해 두었습니다.

 

이제 로그인시 필요한 정보가 모두 모였으니 로그인을 시도합니다. status_code를 확인해 정상적으로 로그인이 되었다면 조회를 시작합니다. 

 

조회 결과를 search_req에 담아둔 뒤 BeautifulSoup를 이용해 객체로 변환합니다.

 

 

모든 HTML이 아닌 위에 보이는 테이블의 헤더와 row정보만 필요하므로 셀렉터를 사용해서 값을 추출해주었습니다.

 

추출한 값을 루프 문을 통해 각각의 이슈를 객체로 만든 뒤 리스트에 담아두었습니다. 모든 작업이 끝나면 시간과 함께 JSON으로 만들기 위해 __issues에 저장한 후 출력까지 해보았습니다.

 

 

값이 위와 같이 잘 나오시나요? 이로써 가장 간단하고 기초적인 크롤러를 만들어보았습니다. 다음에는 이 크롤러를 좀 더 발전시켜보도록 하겠습니다.

 

 

** 본 포스팅의 소스코드는 여기에서 확인할 수 있습니다.

 

 

 

 

 

Redmin에 LDAP 인증을 추가해 계정관리를 해봅니다.

 

 

 

1. Redmine 및 OpenLDAP 준비

 

미리 Redmine과 OpenLDAP 서버를 준비합니다.

 

Redmine은 Docker를 이용하면 매우 간편하게 설치할 수 있습니다.

 

LDAP 서버를 설치하는 방법은 여기를 참고해 주시기 바랍니다.

 

 

 

2. LDAP 설정

 

Redmine에 들어가 설정 > LDAP 인증 > 새 인증 공급자를 눌러줍니다.

 

 

차근차근 채워봅시다.

 

  • 이름: 이 LDAP 인증 공급자의 이름입니다.
  • 호스트: LDAP 인증 서버의 호스트를 적습니다.
  • 포트: LDAP 인증 서버의 LDAP 포트를 적습니다.
  • LDAPS: 보안 프로토콜 사용 여부를 선택합니다.
  • 계정: LDAP 인증 공급자의 계정을 입력합니다.
  • 비밀번호: 위 계정의 암호를 입력합니다.
  • 기본 DN: LDAP의 BaseDN을 입력합니다.
  • LDAP 필터: 비워둡니다.
  • 타임아웃: 타임아웃 시간을 설정합니다
  • 동적 사용자 생성: Redmine에서 LDAP를 이용하기 위해 체크합니다.
  • 로그인 속성: 로그인 id로 사용될 LDAP의 타입입니다.
  • 이름 속성: 이름으로 보일 LDAP의 타입입니다.
  • 성 속성: 성으로 보여질 LDAP의 타입입니다.
  • 메일 속성: 메일로 보여질 LDAP의 타입입니다.

위와 같은 내용으로 다음과 같이 채워줍니다.

 

 

저장 후 테스트를 진행해 봅니다.

 

 

연결 성공 메시지가 보이면 제대로 설정된 것입니다.

 

 

 

3. 유저 생성하기

 

LDAPAdmin을 통해 LDAP로 접속해 유저를 생성합니다.

 

기본적으로 로그인하면 다음과 같은 화면을 확인할 수 있습니다.

 

 

홈 > 우클릭 > New > Organizational unit을 클릭해 새 조직을 생성합니다.

 

새로 생성한 조직을 우클릭한 후 New > User를 선택해 새 유저를 생성합니다.

 

 

다음과 같이 유저 정보를 입력해 줍니다.

 

이제 생성한 유저의 비밀번호를 설정해 줍시다.

 

생성한 유저 우클릭 > Set Password를 클릭한 후 암호를 설정해 저장합니다.

 

 

 

 

4. Redmin 로그인하기

 

이제 생성한 유저로 Redmine에서 로그인을 시도해 봅시다.

 

 

Set Password에서 입력한 비밀번호를 넣어주면

 

 

정상적으로 로그인 되는것을 확인할 수 있습니다.

 

 

 

 

 

 

+ Recent posts