문제

I'm aware that the authentication on the webservicehost class does not adhere fully to authentication standards (returns 403 forbidden rather than prompting for another set of credentials when the user enters incorrect credentials).

I'd still like to implement this basic authentication (username and password at the start of the session, HTTPS unnecessary - see picture below) as it suits my needs for a small home project.

The type of authentication I want

The code I have for myService is as follows:

Imports System.IO
Imports System.Text
Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.ServiceModel.Channels

<ServiceContract()>
Public Class myService
    <OperationContract(), WebGet(UriTemplate:="/xml/{argument1}/{argument2}")>
    Public Function XML(argument1 As String, argument2 As String) As Stream
        requestCounter += 1
        Console.WriteLine("xml data request at " & DateTime.Now.ToString() & ", request count= " & requestCounter)
        Console.WriteLine(WebOperationContext.Current.IncomingRequest.UserAgent.ToString())
        Return _ReturnXML("<xmlresponse><data><argument1>" & argument1 & "</argument1><argument2>" & argument2 & "</argument2></data><server><serverlivesince>" & serverStart.ToString() & "</serverlivesince><pageservetime>" & DateTime.Now.ToString() & "</pageservetime><requestcount>" & requestCounter & "</requestcount></server></xmlresponse>")
        'returns the first two parameters, and the time and date
    End Function

    Private Shared Function _ReturnXML(_result As String) As Stream
        Dim data = Encoding.UTF8.GetBytes(_result)

        WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml; charset=utf-8"
        WebOperationContext.Current.OutgoingResponse.ContentLength = data.Length

        Return New MemoryStream(data)
    End Function
End Class

I then have similar code to return HTML as well as accept other parameter combinations.

In my Main class I've instantiated and opened this service as:

Dim varWebService = New WebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))
varWebService.Open()

Could anyone provide me with code to implement this simple authentication? Or point me to a thorough tutorial? Thanks for any help

도움이 되었습니까?

해결책

You can write a custom WebServiceHost by inheriting from it and change some default parameters like below.

The only change in your code would be

Dim varWebService = New AuthenticatedWebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IdentityModel;
using System.IdentityModel.Selectors;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Security;
using System.ServiceModel.Description;

namespace StackOverflow
{
    public class AuthenticatedWebServiceHost : WebServiceHost
    {
        public AuthenticatedWebServiceHost(Type type, Uri url)
        {
            IDictionary<string, ContractDescription> desc = null;
            base.InitializeDescription(type, new UriSchemeKeyedCollection());
            base.CreateDescription(out desc);
            var val = desc.Values.First();

            WebHttpBinding binding = new WebHttpBinding();
            binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

            base.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
            base.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator();

            base.AddServiceEndpoint(val.ContractType, binding, url);
        }

        //Possible next question:
        //"How can I get the name of the authenticated user?"
        public static string UserName
        {
            get
            {
                if (OperationContext.Current == null) return null;
                if (OperationContext.Current.ServiceSecurityContext == null) return null;
                if (OperationContext.Current.ServiceSecurityContext.PrimaryIdentity == null) return null;
                return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;
            }
        }



        public class CustomUserNamePasswordValidator : UserNamePasswordValidator
        {
            public override void Validate(string userName, string password)
            {
                //Your logic to validate username/password
                if (userName != password)
                    throw new SecurityAccessDeniedException();
            }
        }
    }
}

다른 팁

The answer provided by I4V worked like a charm, converted to VB and copied below in case anyone else needs it in future after spending many hours hunting the web.

The Line to call it is as per the code provided by I4V.

Dim varWebService = New AuthenticatedWebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))

VB.Net Code

Imports System.IdentityModel.Selectors
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports System.ServiceModel.Security
Imports System.ServiceModel.Web

Public Class AuthenticatedWebServiceHost
    Inherits WebServiceHost

    Public Sub New(ByVal type As Type, ByVal url As Uri)
        Dim desc As IDictionary(Of String, ContractDescription) = Nothing
        MyBase.InitializeDescription(type, New UriSchemeKeyedCollection())
        MyBase.CreateDescription(desc)
        Dim val = desc.Values.First()
        Dim binding As WebHttpBinding = New WebHttpBinding()
        binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic
        MyBase.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom
        MyBase.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = New CustomUserNamePasswordValidator()
        MyBase.AddServiceEndpoint(val.ContractType, binding, url)
    End Sub

    Public Shared ReadOnly Property UserName As String
        Get
            If OperationContext.Current Is Nothing Then Return Nothing
            If OperationContext.Current.ServiceSecurityContext Is Nothing Then Return Nothing
            If OperationContext.Current.ServiceSecurityContext.PrimaryIdentity Is Nothing Then Return Nothing
            Return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name
        End Get
    End Property

    Public Class CustomUserNamePasswordValidator
        Inherits UserNamePasswordValidator

        Public Overrides Sub Validate(ByVal userName As String, ByVal password As String)
            If userName <> password Then Throw New SecurityAccessDeniedException()
        End Sub
    End Class
End Class

Shaydo, you are the best! Thank you! That is what I searched for for weeks! I expanded the vb Code in order to use it with https: VB.NET:

Public Class AuthenticatedWebServiceHost
   Inherits WebServiceHost
    Public Sub New(ByVal type As Type, ByVal url As Uri, MyThumbprint As String)
        Dim desc As IDictionary(Of String, ContractDescription) = Nothing
        MyBase.InitializeDescription(type, New UriSchemeKeyedCollection())
        MyBase.CreateDescription(desc)
        Dim val = desc.Values.First()
        Dim binding As WebHttpBinding = New WebHttpBinding()
        'binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly
        binding.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic
        MyBase.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom
        MyBase.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = New CustomUserNamePasswordValidator()
        MyBase.Credentials.ClientCertificate.SetCertificate(System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, MyThumbprint)
        MyBase.AddServiceEndpoint(val.ContractType, binding, url)
    End Sub

    Public Shared ReadOnly Property UserName As String
        Get
            If OperationContext.Current Is Nothing Then Return Nothing
            If OperationContext.Current.ServiceSecurityContext Is Nothing Then Return Nothing
            If OperationContext.Current.ServiceSecurityContext.PrimaryIdentity Is Nothing Then Return Nothing
            Return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name
        End Get
    End Property

    Public Class CustomUserNamePasswordValidator
        Inherits UserNamePasswordValidator
        Public Overrides Sub Validate(ByVal userName As String, ByVal password As String)
            If userName <> password Then
                Console.WriteLine("Error: Access denied")
                Throw New SecurityAccessDeniedException()
            End If
        End Sub
    End Class
End Class
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top