성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <div style='font-family: 맑은 고딕, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>WCF 사용자 정의 인증 구현 예제</div><br /> <br /> 사실 ID/Password 기반으로 WCF를 구현하려 할 때, 윈도우 인증이나 인증서 기반으로 구현하는 것은 일반적인 업무 환경에서 사용하기에는 약간 부적합한 면은 있습니다. 게다가 사용자 정의 인증을 한다 해도 반드시 평문 전달을 막는 안전 장치가 있어야 하는데요. 이 과정에서도 역시 인증서가 꼭 끼게 되는데, 이를 설정하는 WCF 옵션이 하도 다양하다 보니... 관련해서 오해를 하시는 분들이 가끔 있습니다. 마침, 아래의 질문을 하신 분도 있으니... 이참에 사용자 정의 인증 예제를 다뤄보도록 하겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > wcf 인증 문제 ; <a target='_tab' href='/3/0/879'>http://www.sysnet.pe.kr/3/0/879</a> </pre> <br /> 다행히 "사용자 정의 인증" 구현에 대해서 웹 상에 찾아보면 자료가 많이 있고, 저도 아래의 글을 참조해서 실습을 할 텐데 총 4개의 프로젝트로 구성합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > Silverlight 3: Securing your WCF service with a custom username / password authentication mechanism ; <a target='_tab' href='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'>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</a> WCF Authentication: Custom Username and Password Validator ; <a target='_tab' href='https://docs.microsoft.com/en-us/archive/blogs/pedram/wcf-authentication-custom-username-and-password-validator'>https://docs.microsoft.com/en-us/archive/blogs/pedram/wcf-authentication-custom-username-and-password-validator</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <div style='font-family: 맑은 고딕, Consolas; font-size: 12pt; color: #2211AA; text-align: left; font-weight: bold'>1. WcfLibrary - 인터페이스 정의 프로젝트</div><br /> 라이브러리 유형의 프로젝트를 하나 만들고 그 안에 WCF 서비스 인터페이스를 정의합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > // ======= IHelloWorld.cs ======= namespace WcfLibrary { [ServiceContract] public interface IHelloWorld { [OperationContract] string SayHello(); } } </pre> <br /> <div style='font-family: 맑은 고딕, Consolas; font-size: 12pt; color: #2211AA; text-align: left; font-weight: bold'>2. UserNamePasswordAuth - 사용자 정의 인증 모듈 프로젝트</div><br /> <br /> WCF 클라이언트 측에서 전송되는 ID/PW를 확인하는 모듈을 구현합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > // ======= DatabaseBasedValidator.cs ======= namespace UserNamePasswordAuth { public class DatabaseBasedValidator : UserNamePasswordValidator { <b style='COLOR: blue'>public override void Validate(string userName, string password)</b> { if (userName == "test" && password == "test") { return; } throw new <b style='COLOR: blue'>FaultException</b>("Test account can be authenticated ONLY."); } } } </pre> <br /> 재미있는 것은, Validate 메서드 안에서 예외를 발생시키면 인증이 실패하는 것이고 예외 없이 반환하면 인증이 성공한 것입니다. (개인적으로 가장 궁금한 것이... 왜 굳이 예외를 사용해야 했었느냐 하는 것입니다. true/false 반환도 나쁘진 않았을 텐데.)<br /> <br /> 위에서는 간단하게 구현하느라 하드 코딩을 했지만, 원래 Validate 메서드에는 DB에 저장된 ID/PW를 확인하는 작업을 하는 것이 보통이죠.<br /> <br /> <div style='font-family: 맑은 고딕, Consolas; font-size: 12pt; color: #2211AA; text-align: left; font-weight: bold'>3. WcfServer - WCF 서비스 호스트</div><br /> <br /> 서비스 호스트 프로젝트에서 하는 일이 가장 많습니다. ^^<br /> 테스트를 용이하게 하기 위해 콘솔 유형의 프로젝트를 생성하고, 1번과 2번에서 만든 프로젝트를 참조한 후 다음과 같이 IHelloWorld의 구현 클래스를 만들어 줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > // ======= HelloService.cs ======= namespace WcfServer { public class HelloService : IHelloWorld { public string SayHello() { return "Hello: " + OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name; } } } </pre> <br /> 이어서, HelloService 타입을 호스팅합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 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(); } } } } </pre> <br /> binding 설정과 함께 이전에 만들어 둔 UserNamePasswordAuth 모듈을 app.config 파일을 통해서 연결해 줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <?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"> <b style='COLOR: blue'><security mode="Message"> <message clientCredentialType="UserName"/> </security></b> </binding> </netTcpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="helloServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> <serviceCredentials> <b style='COLOR: blue'> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="UserNamePasswordAuth.DatabaseBasedValidator, UserNamePasswordAuth"/></b> <serviceCertificate findValue="myserver" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="Root" /> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration> </pre> <br /> 간단히 특이한 부분만을 살펴보겠습니다. 우선 <security /> 노드에 mode="Message"라고 하면 WCF 메시지가 암호화되어 전송됩니다. 그리고 <message clientCredentialType="UserName"/> 구문을 넣어주어야 WCF 클라이언트로부터 ID/PW를 입력받는 인증방식이 선택되는 것입니다.<br /> <br /> 이전에 만들었던 UserNamePasswordAuth.DatabaseBasedValidator 타입을 다음과 같이 연결해 준 것이 눈에 띕니다.<br /> <br /> <span style='BACKGROUND-COLOR: #ccffcc; FONT-STYLE: italic; MARGIN: 10px 0px 10px 10px; WIDTH: 800px; FONT-FAMILY: 맑은 고딕, Consolas, Verdana; COLOR: #005555'> <userNameAuthentication <b style='COLOR: blue'>userNamePasswordValidationMode="Custom"</b> <br /> <b style='COLOR: blue'>customUserNamePasswordValidatorType="UserNamePasswordAuth.DatabaseBasedValidator, UserNamePasswordAuth"</b>/><br /> </span><br /><br /> <br /> 마지막으로 설정하기 곤란한 것이... 인증서 부분인데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <serviceCertificate findValue="myserver" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="Root" /> </pre> <br /> 위의 정의가 없으면 무조건 예외가 발생합니다. 왜냐하면 클라이언트로부터 전달되는 ID/PW가 평문으로 오기 때문에 심각한 보안 결함이라 여기고 동작을 하지 않는 것입니다. (아니면 HTTPS와 같은 트랜스포트 레벨의 보안이 되는 바인딩을 지정해야 합니다.)<br /> <br /> 여기서 인증서까지 설명하면 너무 길어지기 때문에 넘어가겠습니다. 대신 다음의 글을 참고하시면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 인증서 관련(CER, PVK, SPC, PFX) 파일 만드는 방법 ; <a target='_tab' href='/2/0/863'>http://www.sysnet.pe.kr/2/0/863</a> </pre> <br /> 그렇게 해서 설치한 인증서를 <serviceCertificate /> 노드에 적절하게 설정해 주시면 됩니다. 또는 인증서 자체를 pfx로부터 읽어들여서 지정하는 방법 등 다양하게 있으니 그 부분은 나중에 기회되면 또 설명드리겠습니다. (물론, 웹에 자료는 널려 있습니다.)<br /> <br /> 자... 여기까지 했으면 빌드하고 다른 컴퓨터에서 실행시킵니다. (물론, 인증서는 그 컴퓨터에 등록되어 있어야 합니다.)<br /> <br /> <div style='font-family: 맑은 고딕, Consolas; font-size: 12pt; color: #2211AA; text-align: left; font-weight: bold'>4. WcfClient - WCF 클라이언트</div><br /> <br /> 마지막으로 문제가 되는 WCF 클라이언트입니다. <a target='_tab' href='/3/0/879'>wcf 인증 문제</a>를 물어보신 분은, 바로 이 클라이언트를 구동할 때 서버 측이 사용한 인증서가 클라이언트의 루트 인증서에 등록된 기관으로부터 서명받은 것이어야 한다는 내용인데요. 한번 살펴보겠습니다. 코드를 간단하게 만들기 위해 프록시 클래스 생성은 하지 않고 ChannelFactory를 이용해서 곧바로 사용해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > namespace WcfClient { class Program { static void Main(string[] args) { using (ChannelFactory<IHelloWorld> factory = new ChannelFactory<IHelloWorld>("<b style='COLOR: blue'>TcpNetConf</b>")) { factory.<b style='COLOR: blue'>Credentials.UserName.UserName = "test";</b> factory.<b style='COLOR: blue'>Credentials.UserName.Password = "test";</b> IHelloWorld svc = factory.CreateChannel(); using (svc as IDisposable) { Console.WriteLine(svc.SayHello()); } } } } } </pre> <br /> 중요한 것은 "TcpNetConf"와 연결되는 app.config 설정이죠.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint name="TcpNetConf" address="net.tcp://<b style='COLOR: blue'>myserver</b>:9001/myservice" binding="netTcpBinding" bindingConfiguration="netTcpBindingConf" behaviorConfiguration="netTcpBehavior" contract="WcfLibrary.IHelloWorld"> </endpoint> </client> <bindings> <netTcpBinding> <binding name="netTcpBindingConf"> <b style='COLOR: blue'><security mode="Message" > <message clientCredentialType="UserName"/> </security></b> </binding> </netTcpBinding> </bindings> <behaviors> <endpointBehaviors> <behavior name="netTcpBehavior"> <clientCredentials> <serviceCertificate> <b style='COLOR: blue'><authentication certificateValidationMode="None" /></b> </serviceCertificate> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel> </configuration> </pre> <br /> 좀 특이한 것이 보이나요? ^^<br /> <br /> 우선 당연히 클라이언트 측도 UserName 인증 방식을 사용하도록 <security /> 노드를 구성해야 하고... 아하~~~ 문제는 certificateValidationMode를 None으로 지정해 주면 되는 것이었군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <authentication <b style='COLOR: blue'>certificateValidationMode="None"</b> /> </pre> <br /> 이렇게 해주면 클라이언트에 아무런 인증서를 설치하지 않아도 됩니다. 끝~~~~!<br /> <br /> <hr style='width: 50%' /><br /> <br /> <a target='_tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=529&boardid=331301885'>첨부한 프로젝트</a>는 제가 사용한 예제 프로젝트입니다. (제공되는 mycert.pfx의 암호는 1000입니다.)<br /> <br /> <br /> <br /> <br /> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1363
(왼쪽의 숫자를 입력해야 합니다.)