다음을 통해 공유


사용자 지정 토큰

이 샘플에서는 WCF(Windows Communication Foundation) 애플리케이션에 사용자 지정 토큰 구현을 추가하는 방법을 보여 줍니다. 이 예제에서는 클라이언트 신용 카드에 대한 정보를 서비스에 안전하게 전달하는 데 사용합니다 CreditCardToken . 토큰은 WS-Security 메시지 헤더에 전달되며 메시지 본문 및 기타 메시지 헤더와 함께 대칭 보안 바인딩 요소를 사용하여 서명되고 암호화됩니다. 이는 기본 제공 토큰이 충분하지 않은 경우에 유용합니다. 이 샘플에서는 기본 제공 토큰 중 하나를 사용하는 대신 서비스에 사용자 지정 보안 토큰을 제공하는 방법을 보여 줍니다. 서비스는 요청-회신 통신 패턴을 정의하는 계약을 구현합니다.

비고

이 샘플에 대한 설치 절차 및 빌드 지침은 이 항목의 끝에 있습니다.

요약하자면, 이 샘플에서는 다음을 보여 줍니다.

  • 클라이언트가 사용자 지정 보안 토큰을 서비스에 전달할 수 있는 방법입니다.

  • 서비스에서 사용자 지정 보안 토큰을 사용하고 유효성을 검사하는 방법입니다.

  • WCF 서비스 코드가 사용자 지정 보안 토큰을 포함하여 수신된 보안 토큰에 대한 정보를 가져오는 방법

  • 서버의 X.509 인증서를 사용하여 메시지 암호화 및 서명에 사용되는 대칭 키를 보호하는 방법입니다.

사용자 지정 보안 토큰을 사용한 클라이언트 인증

이 서비스는 BindingHelperEchoServiceHost 클래스를 사용하여 프로그램적으로 생성된 단일 엔드포인트를 제공합니다. 엔드포인트는 주소, 바인딩 및 계약으로 구성됩니다. 바인딩은 SymmetricSecurityBindingElementHttpTransportBindingElement을 사용하여 사용자 지정 바인딩으로 구성됩니다. 이 샘플에서는 서비스의 X.509 인증서를 사용하여 전송 중에 대칭 키를 보호하고 서명되고 암호화된 보안 토큰으로 WS-Security 메시지 헤더에 사용자 지정 SymmetricSecurityBindingElement 을 전달하도록 설정합니다CreditCardToken. 이 동작은 클라이언트 인증에 사용할 서비스 자격 증명과 서비스 X.509 인증서에 대한 정보를 지정합니다.

public static class BindingHelper
{
    public static Binding CreateCreditCardBinding()
    {
        var httpTransport = new HttpTransportBindingElement();

        // The message security binding element will be configured to require a credit card.
        // The token that is encrypted with the service's certificate.
        var messageSecurity = new SymmetricSecurityBindingElement();
        messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new CreditCardTokenParameters());
        X509SecurityTokenParameters x509ProtectionParameters = new X509SecurityTokenParameters();
        x509ProtectionParameters.InclusionMode = SecurityTokenInclusionMode.Never;
        messageSecurity.ProtectionTokenParameters = x509ProtectionParameters;
        return new CustomBinding(messageSecurity, httpTransport);
    }
}

메시지에서 신용 카드 토큰을 사용하기 위해 샘플은 사용자 지정 서비스 자격 증명을 사용하여 이 기능을 제공합니다. 서비스 자격 증명 클래스는 CreditCardServiceCredentials 클래스에 위치하고, EchoServiceHost.InitializeRuntime 메서드에서 서비스 호스트의 동작 컬렉션에 추가됩니다.

class EchoServiceHost : ServiceHost
{
    string creditCardFile;

    public EchoServiceHost(parameters Uri[] addresses)
        : base(typeof(EchoService), addresses)
    {
        creditCardFile = ConfigurationManager.AppSettings["creditCardFile"];
        if (string.IsNullOrEmpty(creditCardFile))
        {
            throw new ConfigurationErrorsException("creditCardFile not specified in service config");
        }

        creditCardFile = String.Format("{0}\\{1}", System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath, creditCardFile);
    }

    override protected void InitializeRuntime()
    {
        // Create a credit card service credentials and add it to the behaviors.
        CreditCardServiceCredentials serviceCredentials = new CreditCardServiceCredentials(this.creditCardFile);
        serviceCredentials.ServiceCertificate.SetCertificate("CN=localhost", StoreLocation.LocalMachine, StoreName.My);
        this.Description.Behaviors.Remove((typeof(ServiceCredentials)));
        this.Description.Behaviors.Add(serviceCredentials);

        // Register a credit card binding for the endpoint.
        Binding creditCardBinding = BindingHelper.CreateCreditCardBinding();
        this.AddServiceEndpoint(typeof(IEchoService), creditCardBinding, string.Empty);

        base.InitializeRuntime();
    }
}

클라이언트 엔드포인트는 서비스 엔드포인트와 비슷한 방식으로 구성됩니다. 클라이언트는 동일한 BindingHelper 클래스를 사용하여 바인딩을 만듭니다. 나머지 설정은 클래스에 Client 있습니다. 또한 클라이언트는 적절한 데이터가 있는 CreditCardToken 인스턴스를 클라이언트 엔드포인트 동작 컬렉션에 추가하여 CreditCardClientCredentials 설치 코드의 X.509 인증서에 포함할 정보 및 서비스 X.509 인증서에 대한 정보를 설정합니다. 샘플에서는 주체 이름이 서비스 인증서로 설정된 CN=localhost X.509 인증서를 사용합니다.

Binding creditCardBinding = BindingHelper.CreateCreditCardBinding();
var serviceAddress = new EndpointAddress("http://localhost/servicemodelsamples/service.svc");

// Create a client with given client endpoint configuration.
channelFactory = new ChannelFactory<IEchoService>(creditCardBinding, serviceAddress);

// Configure the credit card credentials on the channel factory.
var credentials =
      new CreditCardClientCredentials(
      new CreditCardInfo(creditCardNumber, issuer, expirationTime));
// Configure the service certificate on the credentials.
credentials.ServiceCertificate.SetDefaultCertificate(
      "CN=localhost", StoreLocation.LocalMachine, StoreName.My);

// Replace ClientCredentials with CreditCardClientCredentials.
channelFactory.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
channelFactory.Endpoint.Behaviors.Add(credentials);

client = channelFactory.CreateChannel();

Console.WriteLine($"Echo service returned: {client.Echo()}");

((IChannel)client).Close();
channelFactory.Close();

사용자 지정 보안 토큰 구현

WCF에서 사용자 지정 보안 토큰을 사용하도록 설정하려면 사용자 지정 보안 토큰의 개체 표현을 만듭니다. 샘플이 CreditCardToken 클래스에서 이런 형태로 표현됩니다. 개체 표현은 모든 관련 보안 토큰 정보를 보관하고 보안 토큰에 포함된 보안 키 목록을 제공해야 합니다. 이 경우 신용 카드 보안 토큰에는 보안 키가 포함되지 않습니다.

다음 섹션에서는 사용자 지정 토큰을 유선으로 전송하고 WCF 엔드포인트에서 사용할 수 있도록 하기 위해 수행해야 하는 작업을 설명합니다.

class CreditCardToken : SecurityToken
{
    CreditCardInfo cardInfo;
    DateTime effectiveTime = DateTime.UtcNow;
    string id;
    ReadOnlyCollection<SecurityKey> securityKeys;

    public CreditCardToken(CreditCardInfo cardInfo) : this(cardInfo, Guid.NewGuid().ToString()) { }

    public CreditCardToken(CreditCardInfo cardInfo, string id)
    {
        if (cardInfo == null)
            throw new ArgumentNullException(nameof(cardInfo));

        if (id == null)
            throw new ArgumentNullException(nameof(id));

        this.cardInfo = cardInfo;
        this.id = id;

        // The credit card token is not capable of any cryptography.
        this.securityKeys = new ReadOnlyCollection<SecurityKey>(new List<SecurityKey>());
    }

    public CreditCardInfo CardInfo { get { return this.cardInfo; } }

    public override ReadOnlyCollection<SecurityKey> SecurityKeys { get { return this.securityKeys; } }

    public override DateTime ValidFrom { get { return this.effectiveTime; } }
    public override DateTime ValidTo { get { return this.cardInfo.ExpirationDate; } }
    public override string Id { get { return this.id; } }
}

메시지에서 사용자 지정 신용 카드 토큰 가져오기

WCF의 보안 토큰 직렬 변환기는 메시지의 XML에서 보안 토큰의 개체 표현을 만들고 보안 토큰의 XML 형식을 만드는 작업을 담당합니다. 또한 보안 토큰을 가리키는 키 식별자 읽기 및 쓰기와 같은 다른 기능을 담당하지만 이 예제에서는 보안 토큰 관련 기능만 사용합니다. 사용자 지정 토큰을 사용하도록 설정하려면 사용자 고유의 보안 토큰 serializer를 구현해야 합니다. 이 샘플에서는 이 용도로 CreditCardSecurityTokenSerializer 클래스를 사용합니다.

서비스에서 사용자 지정 serializer는 사용자 지정 토큰의 XML 형식을 읽고 사용자 지정 토큰 개체 표현을 만듭니다.

클라이언트에서 클래스는 CreditCardSecurityTokenSerializer 보안 토큰 개체 표현에 포함된 정보를 XML 기록기에 씁니다.

public class CreditCardSecurityTokenSerializer : WSSecurityTokenSerializer
{
    public CreditCardSecurityTokenSerializer(SecurityTokenVersion version) : base() { }

    protected override bool CanReadTokenCore(XmlReader reader)
    {
        XmlDictionaryReader localReader = XmlDictionaryReader.CreateDictionaryReader(reader);

        if (reader == null)
            throw new ArgumentNullException(nameof(reader));

        if (reader.IsStartElement(Constants.CreditCardTokenName, Constants.CreditCardTokenNamespace))
            return true;

        return base.CanReadTokenCore(reader);
    }

    protected override SecurityToken ReadTokenCore(XmlReader reader, SecurityTokenResolver tokenResolver)
    {
        if (reader == null)
            throw new ArgumentNullException(nameof(reader));

        if (reader.IsStartElement(Constants.CreditCardTokenName, Constants.CreditCardTokenNamespace))
        {
            string id = reader.GetAttribute(Constants.Id, Constants.WsUtilityNamespace);

            reader.ReadStartElement();

            // Read the credit card number.
            string creditCardNumber = reader.ReadElementString(Constants.CreditCardNumberElementName, Constants.CreditCardTokenNamespace);

            // Read the expiration date.
            string expirationTimeString = reader.ReadElementString(Constants.CreditCardExpirationElementName, Constants.CreditCardTokenNamespace);
            DateTime expirationTime = XmlConvert.ToDateTime(expirationTimeString, XmlDateTimeSerializationMode.Utc);

            // Read the issuer of the credit card.
            string creditCardIssuer = reader.ReadElementString(Constants.CreditCardIssuerElementName, Constants.CreditCardTokenNamespace);
            reader.ReadEndElement();

            var cardInfo = new CreditCardInfo(creditCardNumber, creditCardIssuer, expirationTime);

            return new CreditCardToken(cardInfo, id);
        }
        else
        {
            return WSSecurityTokenSerializer.DefaultInstance.ReadToken(reader, tokenResolver);
        }
    }

    protected override bool CanWriteTokenCore(SecurityToken token)
    {
        if (token is CreditCardToken)
            return true;
        return base.CanWriteTokenCore(token);
    }

    protected override void WriteTokenCore(XmlWriter writer, SecurityToken token)
    {
        if (writer == null)
            throw new ArgumentNullException(nameof(writer));
        if (token == null)
            throw new ArgumentNullException(nameof(token));

        CreditCardToken c = token as CreditCardToken;
        if (c != null)
        {
            writer.WriteStartElement(Constants.CreditCardTokenPrefix, Constants.CreditCardTokenName, Constants.CreditCardTokenNamespace);
            writer.WriteAttributeString(Constants.WsUtilityPrefix, Constants.Id, Constants.WsUtilityNamespace, token.Id);
            writer.WriteElementString(Constants.CreditCardNumberElementName, Constants.CreditCardTokenNamespace, c.CardInfo.CardNumber);
            writer.WriteElementString(Constants.CreditCardExpirationElementName, Constants.CreditCardTokenNamespace, XmlConvert.ToString(c.CardInfo.ExpirationDate, XmlDateTimeSerializationMode.Utc));
            writer.WriteElementString(Constants.CreditCardIssuerElementName, Constants.CreditCardTokenNamespace, c.CardInfo.CardIssuer);
            writer.WriteEndElement();
            writer.Flush();
        }
        else
        {
            base.WriteTokenCore(writer, token);
        }
    }
}

토큰 공급자 및 토큰 인증자 클래스를 만드는 방법

클라이언트 및 서비스 자격 증명은 보안 토큰 관리자 인스턴스를 제공해야 합니다. 보안 토큰 관리자 인스턴스는 토큰 공급자, 토큰 인증자 및 토큰 serializer를 가져오는 데 사용됩니다.

토큰 공급자는 클라이언트 또는 서비스 자격 증명에 포함된 정보를 기반으로 토큰의 개체 표현을 만듭니다. 토큰 개체 표현은 토큰 serializer를 사용하여 메시지에 기록됩니다(이전 섹션에서 설명).

토큰 인증자는 메시지에 도착하는 토큰의 유효성을 검사합니다. 들어오는 토큰 객체 표현은 토큰 직렬화 도구에 의해 생성됩니다. 그런 다음 이 개체 표현이 유효성 검사를 위해 토큰 인증자에 전달됩니다. 토큰의 유효성이 성공적으로 검사되면 토큰 인증자는 토큰에 포함된 정보를 나타내는 개체 컬렉션을 IAuthorizationPolicy 반환합니다. 이 정보는 나중에 메시지 처리 중에 권한 부여 결정을 수행하고 애플리케이션에 대한 클레임을 제공하는 데 사용됩니다. 이 예제에서는 신용 카드 토큰 인증자가 이 용도로 사용합니다 CreditCardTokenAuthorizationPolicy .

토큰 직렬 변환기는 네트워크를 통해 토큰의 개체 표현을 송신하고 수신하는 작업을 담당합니다. 이 내용은 이전 섹션에서 설명합니다.

이 샘플에서는 클라이언트에서만 토큰 공급자를 사용하고, 클라이언트-서비스 방향으로만 신용 카드 토큰을 전송하려고 하므로 서비스에서만 토큰 인증자를 사용합니다.

클라이언트의 기능은 CreditCardClientCredentials, CreditCardClientCredentialsSecurityTokenManagerCreditCardTokenProvider 클래스에 있습니다.

서비스에서 기능은 CreditCardServiceCredentials, CreditCardServiceCredentialsSecurityTokenManager, CreditCardTokenAuthenticator, 및 CreditCardTokenAuthorizationPolicy 클래스에 상주합니다.

    public class CreditCardClientCredentials : ClientCredentials
    {
        CreditCardInfo creditCardInfo;

        public CreditCardClientCredentials(CreditCardInfo creditCardInfo)
            : base()
        {
            if (creditCardInfo == null)
                throw new ArgumentNullException(nameof(creditCardInfo));

            this.creditCardInfo = creditCardInfo;
        }

        public CreditCardInfo CreditCardInfo
        {
            get { return this.creditCardInfo; }
        }

        protected override ClientCredentials CloneCore()
        {
            return new CreditCardClientCredentials(this.creditCardInfo);
        }

        public override SecurityTokenManager CreateSecurityTokenManager()
        {
            return new CreditCardClientCredentialsSecurityTokenManager(this);
        }
    }

    public class CreditCardClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager
    {
        CreditCardClientCredentials creditCardClientCredentials;

        public CreditCardClientCredentialsSecurityTokenManager(CreditCardClientCredentials creditCardClientCredentials)
            : base (creditCardClientCredentials)
        {
            this.creditCardClientCredentials = creditCardClientCredentials;
        }

        public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
        {
            // Handle this token for Custom.
            if (tokenRequirement.TokenType == Constants.CreditCardTokenType)
                return new CreditCardTokenProvider(this.creditCardClientCredentials.CreditCardInfo);
            // Return server cert.
            else if (tokenRequirement is InitiatorServiceModelSecurityTokenRequirement)
            {
                if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate)
                {
                    return new X509SecurityTokenProvider(creditCardClientCredentials.ServiceCertificate.DefaultCertificate);
                }
            }

            return base.CreateSecurityTokenProvider(tokenRequirement);
        }

        public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)
        {

            return new CreditCardSecurityTokenSerializer(version);
        }

    }

    class CreditCardTokenProvider : SecurityTokenProvider
    {
        CreditCardInfo creditCardInfo;

        public CreditCardTokenProvider(CreditCardInfo creditCardInfo) : base()
        {
            if (creditCardInfo == null)
                throw new ArgumentNullException(nameof(creditCardInfo));

            this.creditCardInfo = creditCardInfo;
        }

        protected override SecurityToken GetTokenCore(TimeSpan timeout)
        {
            SecurityToken result = new CreditCardToken(this.creditCardInfo);
            return result;
        }
    }

    public class CreditCardServiceCredentials : ServiceCredentials
    {
        string creditCardFile;

        public CreditCardServiceCredentials(string creditCardFile)
            : base()
        {
            if (creditCardFile == null)
                throw new ArgumentNullException(nameof(creditCardFile));

            this.creditCardFile = creditCardFile;
        }

        public string CreditCardDataFile
        {
            get { return this.creditCardFile; }
        }

        protected override ServiceCredentials CloneCore()
        {
            return new CreditCardServiceCredentials(this.creditCardFile);
        }

        public override SecurityTokenManager CreateSecurityTokenManager()
        {
            return new CreditCardServiceCredentialsSecurityTokenManager(this);
        }
    }

    public class CreditCardServiceCredentialsSecurityTokenManager : ServiceCredentialsSecurityTokenManager
    {
        CreditCardServiceCredentials creditCardServiceCredentials;

        public CreditCardServiceCredentialsSecurityTokenManager(CreditCardServiceCredentials creditCardServiceCredentials)
            : base(creditCardServiceCredentials)
        {
            this.creditCardServiceCredentials = creditCardServiceCredentials;
        }

        public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
        {
            if (tokenRequirement.TokenType == Constants.CreditCardTokenType)
            {
                outOfBandTokenResolver = null;
                return new CreditCardTokenAuthenticator(creditCardServiceCredentials.CreditCardDataFile);
            }

            return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
        }

        public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)
        {
            return new CreditCardSecurityTokenSerializer(version);
        }
    }

    class CreditCardTokenAuthenticator : SecurityTokenAuthenticator
    {
        string creditCardsFile;
        public CreditCardTokenAuthenticator(string creditCardsFile)
        {
            this.creditCardsFile = creditCardsFile;
        }

        protected override bool CanValidateTokenCore(SecurityToken token)
        {
            return (token is CreditCardToken);
        }

        protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
        {
            CreditCardToken creditCardToken = token as CreditCardToken;

            if (creditCardToken.CardInfo.ExpirationDate < DateTime.UtcNow)
                throw new SecurityTokenValidationException("The credit card has expired.");
            if (!IsCardNumberAndExpirationValid(creditCardToken.CardInfo))
                throw new SecurityTokenValidationException("Unknown or invalid credit card.");

            // the credit card token has only 1 claim - the card number. The issuer for the claim is the
            // credit card issuer

            var cardIssuerClaimSet = new DefaultClaimSet(new Claim(ClaimTypes.Name, creditCardToken.CardInfo.CardIssuer, Rights.PossessProperty));
            var cardClaimSet = new DefaultClaimSet(cardIssuerClaimSet, new Claim(Constants.CreditCardNumberClaim, creditCardToken.CardInfo.CardNumber, Rights.PossessProperty));
            var policies = new List<IAuthorizationPolicy>(1);
            policies.Add(new CreditCardTokenAuthorizationPolicy(cardClaimSet));
            return policies.AsReadOnly();
        }

        /// <summary>
        /// Helper method to check if a given credit card entry is present in the User DB
        /// </summary>
        private bool IsCardNumberAndExpirationValid(CreditCardInfo cardInfo)
        {
            try
            {
                using (var myStreamReader = new StreamReader(this.creditCardsFile))
                {
                    string line = "";
                    while ((line = myStreamReader.ReadLine()) != null)
                    {
                        string[] splitEntry = line.Split('#');
                        if (splitEntry[0] == cardInfo.CardNumber)
                        {
                            string expirationDateString = splitEntry[1].Trim();
                            DateTime expirationDateOnFile = DateTime.Parse(expirationDateString, System.Globalization.DateTimeFormatInfo.InvariantInfo, System.Globalization.DateTimeStyles.AdjustToUniversal);
                            if (cardInfo.ExpirationDate == expirationDateOnFile)
                            {
                                string issuer = splitEntry[2];
                                return issuer.Equals(cardInfo.CardIssuer, StringComparison.InvariantCultureIgnoreCase);
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }
                    return false;
                }
            }
            catch (Exception e)
            {
                throw new Exception("BookStoreService: Error while retrieving credit card information from User DB " + e.ToString());
            }
        }
    }

    public class CreditCardTokenAuthorizationPolicy : IAuthorizationPolicy
    {
        string id;
        ClaimSet issuer;
        IEnumerable<ClaimSet> issuedClaimSets;

        public CreditCardTokenAuthorizationPolicy(ClaimSet issuedClaims)
        {
            if (issuedClaims == null)
                throw new ArgumentNullException(nameof(issuedClaims));
            this.issuer = issuedClaims.Issuer;
            this.issuedClaimSets = new ClaimSet[] { issuedClaims };
            this.id = Guid.NewGuid().ToString();
        }

        public ClaimSet Issuer { get { return this.issuer; } }

        public string Id { get { return this.id; } }

        public bool Evaluate(EvaluationContext context, ref object state)
        {
            foreach (ClaimSet issuance in this.issuedClaimSets)
            {
                context.AddClaimSet(this, issuance);
            }

            return true;
        }
    }

발신자 정보 표시

호출자의 정보를 표시하려면 다음 샘플 코드와 같이 사용합니다 ServiceSecurityContext.Current.AuthorizationContext.ClaimSets . ServiceSecurityContext.Current.AuthorizationContext.ClaimSets 현재 호출자와 연결된 권한 부여 클레임을 포함합니다. 클레임은 CreditCardToken 클래스가 AuthorizationPolicies 컬렉션에서 제공합니다.

bool TryGetStringClaimValue(ClaimSet claimSet, string claimType, out string claimValue)
{
    claimValue = null;
    IEnumerable<Claim> matchingClaims = claimSet.FindClaims(claimType, Rights.PossessProperty);
    if (matchingClaims == null)
        return false;
    IEnumerator<Claim> enumerator = matchingClaims.GetEnumerator();
    enumerator.MoveNext();
    claimValue = (enumerator.Current.Resource == null) ? null :
        enumerator.Current.Resource.ToString();
    return true;
}

string GetCallerCreditCardNumber()
{
     foreach (ClaimSet claimSet in
         ServiceSecurityContext.Current.AuthorizationContext.ClaimSets)
     {
         string creditCardNumber = null;
         if (TryGetStringClaimValue(claimSet,
             Constants.CreditCardNumberClaim, out creditCardNumber))
             {
                 string issuer;
                 if (!TryGetStringClaimValue(claimSet.Issuer,
                        ClaimTypes.Name, out issuer))
                 {
                     issuer = "Unknown";
                 }
                 return $"Credit card '{creditCardNumber}' issued by '{issuer}'";
        }
    }
    return "Credit card is not known";
}

샘플을 실행하면 작업 요청 및 응답이 클라이언트 콘솔 창에 표시됩니다. 클라이언트 창에서 Enter 키를 눌러 클라이언트를 종료합니다.

Batch 파일 설정

이 샘플에 포함된 Setup.bat 일괄 처리 파일을 사용하면 서버 인증서 기반 보안이 필요한 IIS 호스팅 애플리케이션을 실행하도록 관련 인증서를 사용하여 서버를 구성할 수 있습니다. 이 일괄 처리 파일은 컴퓨터 간 작동하거나 비호스팅 환경에서 작동하도록 수정해야 합니다.

다음은 적절한 구성에서 실행되도록 수정할 수 있도록 일괄 처리 파일의 여러 섹션에 대한 간략한 개요를 제공합니다.

  • 서버 인증서 만들기:

    일괄 처리 파일의 Setup.bat 다음 줄은 사용할 서버 인증서를 만듭니다. 변수는 %SERVER_NAME% 서버 이름을 지정합니다. 이 변수를 변경하여 사용자 고유의 서버 이름을 지정합니다. 이 일괄 처리 파일의 기본값은 localhost입니다. 변수를 %SERVER_NAME% 변경하는 경우 Client.cs Service.cs 파일을 살펴보고 localhost의 모든 인스턴스를 Setup.bat 스크립트에서 사용하는 서버 이름으로 바꿔야 합니다.

    인증서는 내(개인) 저장소 LocalMachine 의 저장소 위치에 저장됩니다. 인증서는 IIS 호스팅 서비스에 대한 LocalMachine 저장소에 저장됩니다. 자체 호스팅 서비스의 경우 LocalMachine 문자열을 CurrentUser로 바꿔서 클라이언트 인증서를 CurrentUser 저장소 위치에 저장하도록 일괄 처리 파일을 수정해야 합니다.

    echo ************
    echo Server cert setup starting
    echo %SERVER_NAME%
    echo ************
    echo making server cert
    echo ************
    makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe
    
  • 클라이언트의 신뢰할 수 있는 인증서 저장소에 서버 인증서 설치:

    Setup.bat 일괄 처리 파일의 다음 줄은 서버 인증서를 클라이언트에서 신뢰할 수 있는 사용자 저장소에 복사합니다. Makecert.exe 생성된 인증서는 클라이언트 시스템에서 암시적으로 신뢰할 수 없으므로 이 단계가 필요합니다. 클라이언트에서 신뢰할 수 있는 루트 인증서(예: Microsoft 발급 인증서)에 루트된 인증서가 이미 있는 경우 클라이언트 인증서 저장소를 서버 인증서로 채우는 이 단계는 필요하지 않습니다.

    echo ************
    echo copying server cert to client's TrustedPeople store
    echo ************
    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
    
  • IIS 호스팅 서비스에서 인증서 프라이빗 키에 대한 액세스를 사용하도록 설정하려면 IIS 호스팅 프로세스가 실행되는 사용자 계정에 프라이빗 키에 대한 적절한 권한이 부여되어야 합니다. 이 작업은 Setup.bat 스크립트의 마지막 단계를 통해 수행됩니다.

    echo ************
    echo setting privileges on server certificates
    echo ************
    for /F "delims=" %%i in ('"%ProgramFiles%\ServiceModelSampleTools\FindPrivateKey.exe" My LocalMachine -n CN^=%SERVER_NAME% -a') do set PRIVATE_KEY_FILE=%%i
    set WP_ACCOUNT=NT AUTHORITY\NETWORK SERVICE
    (ver | findstr /C:"5.1") && set WP_ACCOUNT=%COMPUTERNAME%\ASPNET
    echo Y|cacls.exe "%PRIVATE_KEY_FILE%" /E /G "%WP_ACCOUNT%":R
    iisreset
    

비고

Setup.bat 배치 파일은 Visual Studio 명령 프롬프트에서 실행되도록 설계되었습니다. Visual Studio 명령 프롬프트 내에 설정된 PATH 환경 변수는 Setup.bat 스크립트에 필요한 실행 파일이 포함된 디렉터리를 가리킵니다.

샘플을 설정하고 빌드하려면

  1. Windows Communication Foundation 샘플 에 대한One-Time 설정 절차를 수행했는지 확인합니다.

  2. 솔루션을 빌드하려면 Windows Communication Foundation 샘플 빌드의 지침을 따릅니다.

동일한 컴퓨터에서 샘플을 실행하려면

  1. 관리자 권한으로 Visual Studio 명령 프롬프트 창을 열고 샘플 설치 폴더에서 Setup.bat 실행합니다. 그러면 샘플을 실행하는 데 필요한 모든 인증서가 설치됩니다. 경로에 Makecert.exe 있는 폴더가 포함되어 있는지 확인합니다.

비고

샘플을 완료할 때 Cleanup.bat 실행하여 인증서를 제거해야 합니다. 다른 보안 샘플은 동일한 인증서를 사용합니다.

  1. client\bin 디렉터리에서 Client.exe 시작합니다. 클라이언트 활동은 클라이언트 콘솔 애플리케이션에 표시됩니다.

  2. 클라이언트와 서비스가 통신할 수 없는 경우 WCF 샘플대한 문제 해결 팁을 참조하세요.

컴퓨터에서 샘플을 실행하려면

  1. 서비스 컴퓨터에 서비스 이진 파일에 대한 디렉터리를 만듭니다.

  2. 서비스 프로그램 파일을 서비스 컴퓨터의 서비스 디렉터리에 복사합니다. CreditCardFile.txt복사하는 것을 잊지 마세요. 그렇지 않으면 신용 카드 인증자가 클라이언트에서 보낸 신용 카드 정보의 유효성을 검사할 수 없습니다. 또한 서비스 컴퓨터에 Setup.bat 및 Cleanup.bat 파일을 복사합니다.

  3. 컴퓨터의 완전한 도메인 이름이 포함된 주체 이름을 가진 서버 인증서가 필요합니다. %SERVER_NAME% 변수를 서비스가 호스팅되는 컴퓨터의 정규화된 이름으로 변경하면 Setup.bat을 사용하여 하나를 만들 수 있습니다. Setup.bat 파일은 관리자 권한으로 열린 Visual Studio용 개발자 명령 프롬프트에서 실행해야 합니다.

  4. 서버 인증서를 클라이언트의 CurrentUser-TrustedPeople 저장소에 복사합니다. 신뢰할 수 있는 발급자에서 서버 인증서를 발급하지 않은 경우에만 이 작업을 수행해야 합니다.

  5. EchoServiceHost.cs 파일에서 인증서 주체 이름의 값을 변경하여 localhost 대신 정규화된 컴퓨터 이름을 지정합니다.

  6. 언어별 폴더 아래의 \client\bin\ 폴더에서 클라이언트 컴퓨터로 클라이언트 프로그램 파일을 복사합니다.

  7. Client.cs 파일에서 엔드포인트의 주소 값을 서비스의 새 주소와 일치하도록 변경합니다.

  8. Client.cs 파일에서 서비스 X.509 인증서의 주체 이름을 localhost 대신 원격 호스트의 정규화된 컴퓨터 이름과 일치하도록 변경합니다.

  9. 클라이언트 컴퓨터의 명령 프롬프트 창에서 Client.exe 시작합니다.

  10. 클라이언트와 서비스가 통신할 수 없는 경우 WCF 샘플대한 문제 해결 팁을 참조하세요.

샘플 후에 정리하기

  1. 샘플 실행이 완료되면 샘플 폴더에서 Cleanup.bat 실행합니다.