기술 가이드

FreeRADIUS와 LinOTP 2로 OTP 기반 이중 인증 설정 방법

4 min read 인증 업데이트됨 01 Oct 2025
FreeRADIUS + LinOTP 2로 OTP 이중 인증 구성
FreeRADIUS + LinOTP 2로 OTP 이중 인증 구성

목표와 주요 변형 검색어

이 문서는 FreeRADIUS와 LinOTP 2를 연동해 일회용 비밀번호(OTP)를 이용한 이중 인증을 구성하는 방법을 설명합니다. 관련 변형 키워드: FreeRADIUS LinOTP 설정, LinOTP RADIUS 연동, OTP 이중 인증, rlm_perl LinOTP, RADIUS OTP 구성

개요

LinOTP는 다양한 하드웨어 토큰, 소프트웨어 토큰, SMS를 지원하는 일회용 비밀번호(OTP) 백엔드입니다. Enterprise 에디션은 FreeRADIUS용 C 모듈을 제공하지만, Community Edition(AGPLv3)은 제공하지 않습니다. 대신 LinOTP는 간단한 웹 API를 제공하므로 FreeRADIUS에서 HTTP 호출을 통해 인증 여부를 확인할 수 있습니다.

중요: 이 가이드는 개념 증명(pre-beta) 수준의 예제입니다. 운영 환경에서는 SSL 인증서 검증, 중복성, 상세 로깅 및 예외 처리 등을 반드시 구현해야 합니다.

LinOTP 인증 API

LinOTP에서 인증을 확인하는 기본 URL 예시:

https://yourServer/validate/check?user=....&pass=....

또는

https://yourServer/validate/simplecheck?user=...&pass=...

이 API는 특정 사용자에 대해 전달된 OTP가 유효한지 여부를 간단히 응답합니다. Community Edition은 이 HTTP 기반 API를 통해 쉽게 통합됩니다.

FreeRADIUS 쪽 접근 방식

FreeRADIUS에서는 외부 프로그램 실행(rlm_exec) 대신 perl 통합(rlm_perl)을 사용해 LinOTP API를 호출하는 방식이 간단하고 유연합니다. rlm_perl 예제를 인증 흐름(authenticate 함수)만 수정해 LinOTP에 질의하고, 반환 결과에 따라 RADIUS 응답을 결정하면 됩니다.

예제 perl 모듈

아래 perl 스크립트는 rlm_perl 예제를 LinOTP 검증용으로 수정한 것입니다. 실제 운영 전 $URL을 LinOTP 서버로 변경하고 SSL/TLS 검증, 타임아웃, 에러 로그를 강화하세요.

#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#
#  Copyright 2002  The FreeRADIUS server project
#  Copyright 2002  Boian Jordanov <[email protected]>
#  Copyright 2011  linotp project <[email protected]>

#
# Based on the Example code for use with rlm_perl
#
#

=head1 NAME

freeradius_perl - Perl module for use with FreeRADIUS rlm_perl, to authenticate against 
 LinOTP  http://www.linotp.org

=head1 SYNOPSIS

   use with freeradius:  
   
   Configure rlm_perl to work with LinOTP:
   in /etc/freeradius/users 
    set:
     DEFAULT Auth-type := perl

  in /etc/freeradius/modules/perl
     point
     perl {
         module = 
 to this file

  in /etc/freeradius/sites-enabled/
  set
  authenticate{
    perl
    [....]

=head1 DESCRIPTION

This module enables freeradius to authenticate using LinOTP.

   TODO: 
     * checking of server certificate


=head2 Methods

   * authenticate

=head1 AUTHOR

Cornelius Koelbel ([email protected])

=head1 COPYRIGHT

Copyright 2011 

This library is free software; you can redistribute it 
under the GPLv2.

=head1 SEE ALSO

perl(1).

=cut

use strict;
use LWP 5.64;


# use ...
# This is very important ! Without this script will not get the filled  hashesh from main.
use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK $URL);
use Data::Dumper;

$URL = "https://localhost/validate/simplecheck";

# This is hash wich hold original request from radius
#my %RAD_REQUEST;
# In this hash you add values that will be returned to NAS.
#my %RAD_REPLY;
#This is for check items
#my %RAD_CHECK;

#
# This the remapping of return values
#
       use constant    RLM_MODULE_REJECT=>    0;#  /* immediately reject the request */
       use constant    RLM_MODULE_FAIL=>      1;#  /* module failed, don't reply */
       use constant    RLM_MODULE_OK=>        2;#  /* the module is OK, continue */
       use constant    RLM_MODULE_HANDLED=>   3;#  /* the module handled the request, so stop. */
       use constant    RLM_MODULE_INVALID=>   4;#  /* the module considers the request invalid. */
       use constant    RLM_MODULE_USERLOCK=>  5;#  /* reject the request (user is locked out) */
       use constant    RLM_MODULE_NOTFOUND=>  6;#  /* user not found */
       use constant    RLM_MODULE_NOOP=>      7;#  /* module succeeded without doing anything */
       use constant    RLM_MODULE_UPDATED=>   8;#  /* OK (pairs modified) */
       use constant    RLM_MODULE_NUMCODES=>  9;#  /* How many return codes there are */

# Function to handle authorize
sub authorize {
       # For debugging purposes only
#       &log_request_attributes;

       # Here's where your authorization code comes
       # You can call another function from here:
       &test_call;

       return RLM_MODULE_OK;
}

# Function to handle authenticate
sub authenticate {
       # For debugging purposes only
#       &log_request_attributes;

     
    my $ua = LWP::UserAgent->new();
    my $req = HTTP::Request->new(GET => $URL . "?user=" .
        $RAD_REQUEST{'User-Name'} . "&pass=" . 
        $RAD_REQUEST{'User-Password'} );
    my $response = $ua->request( $req );

    die "Error at $URL\n ", $response->status_line, "\n Aborting"
      unless $response->is_success;
      
    if($response->content =~ m/:\-\)/i) {
               return RLM_MODULE_OK;
       } else {
       $RAD_REPLY{'Reply-Message'} = "LinOTP server denied access!";
               return RLM_MODULE_REJECT;
    }
}

# Function to handle preacct
sub preacct {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle accounting
sub accounting {
       # For debugging purposes only
#       &log_request_attributes;

       # You can call another subroutine from here
       &test_call;

       return RLM_MODULE_OK;
}

# Function to handle checksimul
sub checksimul {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle pre_proxy
sub pre_proxy {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle post_proxy
sub post_proxy {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle post_auth
sub post_auth {
       # For debugging purposes only
#       &log_request_attributes;

       return RLM_MODULE_OK;
}

# Function to handle xlat
sub xlat {
       # For debugging purposes only
#       &log_request_attributes;

       # Loads some external perl and evaluate it
       my ($filename,$a,$b,$c,$d) = @_;
       &radiusd::radlog(1, "From xlat $filename ");
       &radiusd::radlog(1,"From xlat $a $b $c $d ");
       local *FH;
       open FH, $filename or die "open '$filename' $!";
       local($/) = undef;
       my $sub = ;
       close FH;
       my $eval = qq{ sub handler{ $sub;} };
       eval $eval;
       eval {main->handler;};
}

# Function to handle detach
sub detach {
       # For debugging purposes only
#       &log_request_attributes;

       # Do some logging.
       &radiusd::radlog(0,"rlm_perl::Detaching. Reloading. Done.");
} 

#
# Some functions that can be called from other functions
#

sub test_call {
       # Some code goes here
}

sub log_request_attributes {
       # This shouldn't be done in production environments!
       # This is only meant for debugging!
       for (keys %RAD_REQUEST) {
               &radiusd::radlog(1, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}");
       }
}

1;

참고: 위 스크립트에서 $URL 변수를 실제 LinOTP 서버의 검증 엔드포인트(예: https://linotp.example/validate/simplecheck)로 변경하세요. 또한 LWP::UserAgent 기본 설정으로는 SSL 검증을 하지 않을 수 있으니, 운영 환경에서는 SSL 검증을 명시적으로 활성화하세요.

FreeRADIUS 설정 요약

  • /etc/freeradius/users에서 DEFAULT Auth-Type := perl 설정
  • /etc/freeradius/modules/perl에서 module 경로를 예제 perl 파일로 지정
  • 사이트 설정(/etc/freeradius/sites-enabled/)의 authenticate 섹션에서 perl 호출 추가

운영에서 고려할 보안 및 신뢰성 강화

  • SSL/TLS 검증: LWP::UserAgent에 SSL 인증서 검증을 강제하세요. 서버 인증서가 자체 서명일 경우 CA로 신뢰하도록 구성하세요.
  • 타임아웃 및 재시도: 네트워크 장애에 대비해 HTTP 타임아웃과 재시도를 구현하세요. 무한 대기나 블로킹 호출을 피하세요.
  • 로깅: 실패 원인(네트워크, 500 응답, 파싱 실패 등)을 상세히 로깅하되, OTP 자체나 민감한 패스워드는 로그에 남기지 마세요.
  • 고가용성: LinOTP 서버를 다중화하거나 로드밸런서를 통해 중단 위험을 줄이세요.
  • 권한: FreeRADIUS와 perl 스크립트 파일의 파일 권한을 최소화하세요.

대체 접근법

  • rlm_exec 사용: 외부 바이너리나 스크립트를 호출해 LinOTP API를 호출하는 방식. 디버깅이 수월하지만 프로세스 생성 비용이 큼.
  • Enterprise C 모듈: LinOTP Enterprise가 제공하는 C 모듈을 사용하면 더 낮은 레이턴시와 안정성을 얻을 수 있음(상업적 옵션).
  • 프록시 서비스: 내부에 작은 인증 프록시를 두고 FreeRADIUS는 로컬 프록시와 통신하게 하여 복잡도를 분리.

실패 사례와 주의점

  • 네트워크 단절 시 모든 인증 요청이 실패할 수 있음(서비스 가용성 저하). 대비책: 캐시 기반 예비 인증, 또는 이중화.
  • SSL 미검증: 중간자 공격(MITM)에 취약. 운영에서는 항상 인증서를 검증하세요.
  • 응답 포맷 변화: LinOTP의 응답 패턴이 바뀌면 정규식 매칭이 실패할 수 있음. API 스펙을 확인하고 견고한 파싱을 구현하세요.

역할별 체크리스트

  • 시스템 관리자:
    • $URL을 실제 서버로 설정하고 파일 권한 확인
    • 방화벽에서 FreeRADIUS와 LinOTP 간 통신 허용
  • 보안 책임자:
    • SSL 인증서 정책 검토 및 모니터링
    • OTP/로그 보관 정책 결정
  • 네트워크 운영자:
    • LinOTP 서버에 대한 가용성(로드밸런서/백업) 구성
    • 모니터링 임계치 및 알람 설정

Критерии приёмки

  • FreeRADIUS가 LinOTP에 질의하여 유효한 OTP에 대해 ACCESS-ACCEPT를 반환한다.
  • 잘못된 OTP에 대해 ACCESS-REJECT를 반환한다.
  • 네트워크 실패 시 적절한 로그가 수집되고, 재시도 정책이 동작한다.
  • SSL 인증서 검증이 적용되어 중간자공격으로부터 보호된다.

간단한 점검(테스트 케이스)

  • 정상 케이스: 올바른 사용자명과 유효한 OTP를 전송해 인증 성공 확인
  • 오류 케이스: 잘못된 OTP로 인증 실패 확인 및 Reply-Message 확인
  • 네트워크 오류: LinOTP 서비스 차단 시 FreeRADIUS가 적절히 실패 처리(로그/거부)하는지 확인

요약

  • LinOTP Community는 간단한 HTTP API로 FreeRADIUS와 쉽게 통합됩니다.
  • rlm_perl을 사용하면 Perl에서 LinOTP API를 호출해 인증 흐름을 제어할 수 있습니다.
  • 운영 환경에서는 SSL 검증, 로깅, 타임아웃/재시도, 고가용성 구성 등 추가 작업이 필요합니다.

요점: 이 예제는 빠른 통합을 위한 출발점입니다. 실제 서비스에 적용하기 전 보안·운영 요구사항을 채워 넣으세요.

공유하기: X/Twitter Facebook LinkedIn Telegram
저자
편집

유사한 자료

Microsoft Teams에서 사라진 채널 복구 방법
협업 도구

Microsoft Teams에서 사라진 채널 복구 방법

Windows 11에서 화면 새로고침하는 방법
윈도우 가이드

Windows 11에서 화면 새로고침하는 방법

Server 2008 R2에서 RDS로 터미널 서버 설정하기
IT 관리

Server 2008 R2에서 RDS로 터미널 서버 설정하기

커널 수준 악성코드 이해 및 방어 가이드
보안

커널 수준 악성코드 이해 및 방어 가이드

YouTube 싫어요 수 확인법 — 브라우저과 Android
가이드

YouTube 싫어요 수 확인법 — 브라우저과 Android

FreeRADIUS + LinOTP 2로 OTP 이중 인증 구성
인증

FreeRADIUS + LinOTP 2로 OTP 이중 인증 구성