Microsoft MVP성태의 닷넷 이야기
.NET Framework: 178. WCF - 사용자 정의 인증 구현 예제 [링크 복사], [링크+제목 복사],
조회: 26659
글쓴 사람
정성태 (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)
13260정성태2/14/20234326오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/20234488.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/20234330오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/20234397.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [1]파일 다운로드1
13256정성태2/10/20235246개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
13255정성태2/10/20234580오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/20234508Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/20235257Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
13252정성태2/9/20234080오류 유형: 844. ssh로 명령어 수행 시 멈춤 현상
13251정성태2/8/20234534스크립트: 44. 파이썬의 3가지 스레드 ID
13250정성태2/8/20236372오류 유형: 843. System.InvalidOperationException - Unable to configure HTTPS endpoint
13249정성태2/7/20235208오류 유형: 842. 리눅스 - You must wait longer to change your password
13248정성태2/7/20234243오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/20235126VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/20234288개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/20234866.NET Framework: 2093. C# - PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
13244정성태2/5/20234192VS.NET IDE: 179. Visual Studio - External Tools에 Shell 내장 명령어 등록
13243정성태2/5/20235035디버깅 기술: 190. windbg - Win32 API 호출 시점에 BP 거는 방법 [1]
13242정성태2/4/20234482디버깅 기술: 189. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.UnauthorizedAccessException
13241정성태2/3/20233955디버깅 기술: 188. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.IO.FileNotFoundException
13240정성태2/1/20234110디버깅 기술: 187. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.Web.HttpException
13239정성태2/1/20233785디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/20235877.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/20235556.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
13236정성태1/29/20235127개발 환경 구성: 663. openssl을 이용해 인트라넷 IIS 사이트의 SSL 인증서 생성
13235정성태1/29/20234684개발 환경 구성: 662. openssl - 윈도우 환경의 명령행에서 SAN 적용하는 방법
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...