Microsoft MVP성태의 닷넷 이야기
.NET Framework: 178. WCF - 사용자 정의 인증 구현 예제 [링크 복사], [링크+제목 복사]
조회: 26622
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 3개 있습니다.)
WCF 사용자 정의 인증 구현 예제


사실 ID/Password 기반으로 WCF를 구현하려 할 때, 윈도우 인증이나 인증서 기반으로 구현하는 것은 일반적인 업무 환경에서 사용하기에는 약간 부적합한 면은 있습니다. 게다가 사용자 정의 인증을 한다 해도 반드시 평문 전달을 막는 안전 장치가 있어야 하는데요. 이 과정에서도 역시 인증서가 꼭 끼게 되는데, 이를 설정하는 WCF 옵션이 하도 다양하다 보니... 관련해서 오해를 하시는 분들이 가끔 있습니다. 마침, 아래의 질문을 하신 분도 있으니... 이참에 사용자 정의 인증 예제를 다뤄보도록 하겠습니다.

wcf 인증 문제
; https://www.sysnet.pe.kr/3/0/879

다행히 "사용자 정의 인증" 구현에 대해서 웹 상에 찾아보면 자료가 많이 있고, 저도 아래의 글을 참조해서 실습을 할 텐데 총 4개의 프로젝트로 구성합니다.

Silverlight 3: Securing your WCF service with a custom username / password authentication mechanism 
; http://blogs.infosupport.com/blogs/alexb/archive/2009/10/02/silverlight-3-securing-your-wcf-service-with-a-custom-username-and-password-authentication-mechanism.aspx

WCF Authentication: Custom Username and Password Validator 
; https://docs.microsoft.com/en-us/archive/blogs/pedram/wcf-authentication-custom-username-and-password-validator




1. WcfLibrary - 인터페이스 정의 프로젝트

라이브러리 유형의 프로젝트를 하나 만들고 그 안에 WCF 서비스 인터페이스를 정의합니다.

// ======= IHelloWorld.cs =======

namespace WcfLibrary
{
    [ServiceContract]
    public interface IHelloWorld
    {
        [OperationContract]  
        string SayHello();    
    }
}

2. UserNamePasswordAuth - 사용자 정의 인증 모듈 프로젝트


WCF 클라이언트 측에서 전송되는 ID/PW를 확인하는 모듈을 구현합니다.

// ======= DatabaseBasedValidator.cs =======

namespace UserNamePasswordAuth
{
    public class DatabaseBasedValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName == "test" && password == "test")
            {
                return;
            }

            throw new FaultException("Test account can be authenticated ONLY.");
        }
    }
}

재미있는 것은, Validate 메서드 안에서 예외를 발생시키면 인증이 실패하는 것이고 예외 없이 반환하면 인증이 성공한 것입니다. (개인적으로 가장 궁금한 것이... 왜 굳이 예외를 사용해야 했었느냐 하는 것입니다. true/false 반환도 나쁘진 않았을 텐데.)

위에서는 간단하게 구현하느라 하드 코딩을 했지만, 원래 Validate 메서드에는 DB에 저장된 ID/PW를 확인하는 작업을 하는 것이 보통이죠.

3. WcfServer - WCF 서비스 호스트


서비스 호스트 프로젝트에서 하는 일이 가장 많습니다. ^^
테스트를 용이하게 하기 위해 콘솔 유형의 프로젝트를 생성하고, 1번과 2번에서 만든 프로젝트를 참조한 후 다음과 같이 IHelloWorld의 구현 클래스를 만들어 줍니다.

// ======= HelloService.cs =======

namespace WcfServer
{
    public class HelloService : IHelloWorld
    {
        public string SayHello()
        {
            return "Hello: " + OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;
        }
    }
}

이어서, HelloService 타입을 호스팅합니다.

namespace WcfServer
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost serviceHost = new ServiceHost(typeof(HelloService)))
            {
                serviceHost.Open();

                Console.WriteLine("Press any key to exit...");
                Console.ReadLine();
            }
        }
    }
}

binding 설정과 함께 이전에 만들어 둔 UserNamePasswordAuth 모듈을 app.config 파일을 통해서 연결해 줍니다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="helloServiceBehavior" 
               name="WcfServer.HelloService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:9000"/>
            <add baseAddress="net.tcp://localhost:9001"/>
          </baseAddresses> 
        </host>  
        <endpoint address="myservice" binding="netTcpBinding" 
                  bindingConfiguration="netTcpBindingConf" 
                  contract="WcfLibrary.IHelloWorld">
          </endpoint>  
        <endpoint address="mex" binding="mexHttpBinding" 
                  contract="IMetadataExchange"/>  
      </service>  
    </services>  
    
    <bindings>
      <netTcpBinding>
        <binding name="netTcpBindingConf">
            <security mode="Message">
              <message clientCredentialType="UserName"/> 
            </security>  
        </binding>  
      </netTcpBinding>  
    </bindings>
    
    <behaviors>
      <serviceBehaviors>
        <behavior name="helloServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/> 
            <serviceDebug includeExceptionDetailInFaults="true"/> 
            <serviceCredentials>
              <userNameAuthentication userNamePasswordValidationMode="Custom"  
                                          customUserNamePasswordValidatorType="UserNamePasswordAuth.DatabaseBasedValidator, UserNamePasswordAuth"/>
              <serviceCertificate
                     findValue="myserver"
                     x509FindType="FindBySubjectName"
                     storeLocation="LocalMachine"
                     storeName="Root" />

            </serviceCredentials>  
        </behavior>  
      </serviceBehaviors>  
    </behaviors> 
  </system.serviceModel>  
  
</configuration>

간단히 특이한 부분만을 살펴보겠습니다. 우선 <security /> 노드에 mode="Message"라고 하면 WCF 메시지가 암호화되어 전송됩니다. 그리고 <message clientCredentialType="UserName"/> 구문을 넣어주어야 WCF 클라이언트로부터 ID/PW를 입력받는 인증방식이 선택되는 것입니다.

이전에 만들었던 UserNamePasswordAuth.DatabaseBasedValidator 타입을 다음과 같이 연결해 준 것이 눈에 띕니다.

<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="UserNamePasswordAuth.DatabaseBasedValidator, UserNamePasswordAuth"/>



마지막으로 설정하기 곤란한 것이... 인증서 부분인데요.

<serviceCertificate
        findValue="myserver"
        x509FindType="FindBySubjectName"
        storeLocation="LocalMachine"
        storeName="Root" />

위의 정의가 없으면 무조건 예외가 발생합니다. 왜냐하면 클라이언트로부터 전달되는 ID/PW가 평문으로 오기 때문에 심각한 보안 결함이라 여기고 동작을 하지 않는 것입니다. (아니면 HTTPS와 같은 트랜스포트 레벨의 보안이 되는 바인딩을 지정해야 합니다.)

여기서 인증서까지 설명하면 너무 길어지기 때문에 넘어가겠습니다. 대신 다음의 글을 참고하시면 됩니다.

인증서 관련(CER, PVK, SPC, PFX) 파일 만드는 방법
; https://www.sysnet.pe.kr/2/0/863

그렇게 해서 설치한 인증서를 <serviceCertificate /> 노드에 적절하게 설정해 주시면 됩니다. 또는 인증서 자체를 pfx로부터 읽어들여서 지정하는 방법 등 다양하게 있으니 그 부분은 나중에 기회되면 또 설명드리겠습니다. (물론, 웹에 자료는 널려 있습니다.)

자... 여기까지 했으면 빌드하고 다른 컴퓨터에서 실행시킵니다. (물론, 인증서는 그 컴퓨터에 등록되어 있어야 합니다.)

4. WcfClient - WCF 클라이언트


마지막으로 문제가 되는 WCF 클라이언트입니다. wcf 인증 문제를 물어보신 분은, 바로 이 클라이언트를 구동할 때 서버 측이 사용한 인증서가 클라이언트의 루트 인증서에 등록된 기관으로부터 서명받은 것이어야 한다는 내용인데요. 한번 살펴보겠습니다. 코드를 간단하게 만들기 위해 프록시 클래스 생성은 하지 않고 ChannelFactory를 이용해서 곧바로 사용해 보겠습니다.

namespace WcfClient
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChannelFactory<IHelloWorld> factory =
                new ChannelFactory<IHelloWorld>("TcpNetConf"))
            {
                factory.Credentials.UserName.UserName = "test";
                factory.Credentials.UserName.Password = "test";

                IHelloWorld svc = factory.CreateChannel();
                using (svc as IDisposable)
                {
                    Console.WriteLine(svc.SayHello());
                }
            }           
        }
    }
}

중요한 것은 "TcpNetConf"와 연결되는 app.config 설정이죠.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="TcpNetConf"
        address="net.tcp://myserver:9001/myservice"
        binding="netTcpBinding"
        bindingConfiguration="netTcpBindingConf"
        behaviorConfiguration="netTcpBehavior"
        contract="WcfLibrary.IHelloWorld">

      </endpoint>
    </client>

    <bindings>
      <netTcpBinding>
        <binding name="netTcpBindingConf">
          <security mode="Message" >
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </netTcpBinding>
    </bindings>

    <behaviors>
      <endpointBehaviors>
        <behavior name="netTcpBehavior">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="None" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

좀 특이한 것이 보이나요? ^^

우선 당연히 클라이언트 측도 UserName 인증 방식을 사용하도록 <security /> 노드를 구성해야 하고... 아하~~~ 문제는 certificateValidationMode를 None으로 지정해 주면 되는 것이었군요. ^^

<authentication certificateValidationMode="None" />

이렇게 해주면 클라이언트에 아무런 인증서를 설치하지 않아도 됩니다. 끝~~~~!




첨부한 프로젝트는 제가 사용한 예제 프로젝트입니다. (제공되는 mycert.pfx의 암호는 1000입니다.)







[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/8/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2010-05-06 09시36분
[정용훈] 질문 드렸던 사람입니다. 많은도움되었습니다.^^
그런데 Message레벨 암호화가 되는 바람에 속도가 너무 느려졌습니다.
무암호화 일때는 겨우 원하는 만큼의 속도가 나왔습니다. 하지만 암호화 일때는 이게 최신 컴퓨터에서 돌아가는 통신 속도가 맞는지 의심스러울정도로 느려지는군요.
메세지가 발생하는 속도보다 처리하는 속도가 느려서 오류가 발생하는 문제가 생겼습니다.
UserNamePasswordValidator에 무암호화 방식은 없는지 궁금합니다.~~
[guest]
2010-05-08 01시24분
무암호화 방식이라 하면... 이미 ID/PW가 평문으로 넘어간다는 것인데, 이런 경우라면 별다른 표준이 정해져 있지는 않습니다. WS-* 표준을 구현한 WCF 문제를 벗어난 것입니다.

대신에 그런 경우에는 생각을 조금 바꾸시면 되지 않을까요? 예를 들어, 사용자 정의 헤더로 UserName, Password를 정의하고 넘겨준다거나... 하는 것과 별반 다르지 않을 것 같습니다
kevin25
2012-09-12 10시52분
[Foxpro87] 좋은글 잘 보았습니다. 실버라이트로 인증방법을 찾고 있었습니다.
첨부한 프로젝트를 실행했는데 오류가 납니다. - 다음 검색 조건을 사용하여 X.509 인증서를 찾을 수 없습니다. StoreName 'Root', StoreLocation 'LocalMachine', FindType 'FindBySubjectName', FindValue 'myserver'.
인터넷익스플로러 옵션>내용>인증서 에서 설치된것은 확인을 했습니다. (인증서관련은 처음이라 제대로 설치했는지 확실하지 않습니다.)

"자... 여기까지 했으면 빌드하고 다른 컴퓨터에서 실행시킵니다. (물론, 인증서는 그 컴퓨터에 등록되어 있어야 합니다.)"
위에 다른 컴퓨터에서 실행한다는 의미를 잘 모르겠습니다. 인증서가 필요없는 WCF 프로젝트는 서버/Client 동일 컴퓨터에서 동작하는 거 같더군요.
다른 컴퓨터라고 하면 서버프로젝트는 A컴퓨터에서 실버라이트는 Client프로젝트는 B컴퓨터에서 실행한다는 의미인가요? (초보적인 질문을 드려 죄송합니다. ^^)
[guest]
2012-09-12 01시19분
일단, 아래의 글을 먼저 읽어보신 다음에 다시 질문해 주세요. ^^

윈도우즈 인증서 서비스 이야기
; http://www.sysnet.pe.kr/2/0/353
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13607정성태4/25/2024185닷넷: 2248.C# - 인터페이스 타입의 다중 포인터를 인자로 갖는 C/C++ 함수 연동
13606정성태4/24/2024198닷넷: 2247. C# - tensorflow 연동 (MNIST 예제)파일 다운로드1
13605정성태4/23/2024341닷넷: 2246. C# - Python.NET을 이용한 파이썬 소스코드 연동파일 다운로드1
13604정성태4/22/2024375오류 유형: 901. Visual Studio - Unable to set the next statement. Set next statement cannot be used in '[Exception]' call stack frames.
13603정성태4/21/2024693닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동파일 다운로드1
13602정성태4/20/2024801닷넷: 2244. C# - PCM 오디오 데이터를 연속(Streaming) 재생 (Windows Multimedia)파일 다운로드1
13601정성태4/19/2024847닷넷: 2243. C# - PCM 사운드 재생(NAudio)파일 다운로드1
13600정성태4/18/2024869닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024867닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024889닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드2
13597정성태4/15/2024875닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/20241063닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/20241051닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241069닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241084닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241219C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241198닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241079Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241154닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241267닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신 [2]파일 다운로드1
13587정성태3/27/20241170오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241334Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241115Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241065개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
13583정성태3/25/20241303Windows: 261. CPU Utilization이 100% 넘는 경우를 성능 카운터로 확인하는 방법
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...