苦于认证的方法

时间:2012-04-22 09:00:41

标签: c# web-services rest authentication encryption

我想,不使用内置的WCF / c#组件,

  1. 将客户端验证为RESTful服务
  2. 处理客户端API调用中的身份验证失败
  3. 这是一个教学练习:我意识到有内置的身份验证方法,我想从头开始学习它是如何工作的。

    我有密码哈希和检查逻辑以及验证密码的公开REST调用,但我不确定如何从那里继续。

    背景

    我正在努力为我的休息服务创建一个身份验证方法。

    到目前为止,我已设法创建密码,盐的哈希并存储盐,我已设法验证用户。但是我不确定如何封装我的所有wcf REST请求,以便如果有任何请求(GET,POST),它会要求您登录,如果您登录没有。

    因为我使用了自己的身份验证技术,而且我是Web服务和C#的新手,我真的不知道从哪里开始?

    所以我将向任何可以提供方法的人提供300个代表。

    代码

    这是我的休息服务:

    [ServiceContract(Namespace = "http://tempuri.org")]
    [XmlSerializerFormat]
    public interface IService
    {
      .... all of my GET, POST, PUT and DELETE requests
    {
    [DataContract(Name="Student")]
    [Serializable]
    public class Student
    {
        [DataMember(Name = "StudentID")]
        public string StudentID { get; set; }
        [DataMember(Name = "FirstName")]
        public string FirstName { get; set; }
        [DataMember(Name = "LastName")]
        public string LastName { get; set; }
        [DataMember(Name = "Password")]
        public string Password;
        [DataMember(Name = "Salt")]
        public byte[] Salt;
        //note the use of public datamembers for password and salt, not sure how to implement private for this. 
     }
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    [Serializable]
    public class Service: IService
    {
        #region Authentication, hash and salt
        protected RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
        public byte[] GenerateSalt() //Generate random salt for each password
        {
            byte[] salt = new byte[10000]; 
            random.GetNonZeroBytes(salt);
            return salt;
        }
        public static byte[] Hash(string value, byte[] salt) //hash and salt the password 
        {
            return Hash(Encoding.UTF8.GetBytes(value), salt); 
        }
    
        public static byte[] Hash(byte[] value, byte[] salt) // create hash of password 
        {
            byte[] saltedValue = value.Concat(salt).ToArray();
    
            return new SHA256Managed().ComputeHash(saltedValue); //initialise new isntance of the crypto class using SHA-256/32-byte (256 bits) words  
        }
        public string AuthenticateUser(string studentID, string password) //Authentication should always be done server side 
        {
            var result = students.FirstOrDefault(n => n.StudentID == studentID);
            //find the StudentID that matches the string studentID 
            if (result != null)
            //if result matches then do this
            {
                byte[] passwordHash = Hash(password, result.Salt);
                string HashedPassword = Convert.ToBase64String(passwordHash);
                //hash salt the string password
                if (HashedPassword == result.Password)
                //check if the HashedPassword (string password) matches the stored student.Password
                {
                    return result.StudentID;
                    // if it does return the Students ID                     
                }
    
    
            }
            return "Login Failed";
            //if it doesnt return login failed 
        }
        #endregion 
    

    我正在从控制台应用程序托管,我没有web.config文件或app.config文件。因为我做了自己的身份验证方法,我不确定基本身份验证是否有效。

    我也不想保留会话以保持服务SOA和无状态。

    控制台应用:

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
                ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
                WebHttpBinding binding = new WebHttpBinding();
                binding.Security.Mode = WebHttpSecurityMode.Transport;
                host.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
                host.Open();
                Console.WriteLine("Host opened");
                Console.ReadLine();
    
            }
        }
    }
    

    请注意,在我的客户端,我做了一些非常基本的事情来进行身份验证:

        private void Login_Click(object sender, RoutedEventArgs e)
        {
    
            //Authenticate user (GET Request)
            string uri = string.Format("http://localhost:8000/Service/AuthenticateUser/{0}/{1}", textBox1.Text, passwordBox1.Password);
            XDocument xDoc = XDocument.Load(uri);
            string UserAuthenticationID = xDoc.Element("string").Value;
            Int32 value;
            if (Int32.TryParse(UserAuthenticationID, out value))
            {
                MainWindow authenticatedidentification = new MainWindow(); 
                authenticatedidentification.SetLabel(UserAuthenticationID);
                authenticatedidentification.Show();
                this.Close();
            }
            else
            {
                label1.Content = UserAuthenticationID;
            }
        }
    

    所以我不确定如果有上述任何内容,还有什么东西必须进入主应用程序,以便主应用程序访问这些休息请求。

4 个答案:

答案 0 :(得分:0)

所以通常这样做的方式是

  1. 客户端通过身份验证服务调用提供一些凭据
  2. 服务验证这些凭据并交回一些身份验证令牌。
  3. 后续调用已使用该令牌进行身份验证。

    这可以通过发送令牌(例如http digest authentication)或更安全的方式来完成,令牌是用于计算参数上的message authentication code的密钥。这可以防止任何人篡改请求。

  4. 关于如何在WCF here中执行此操作,有一个不错的长期讨论。请参阅“安全注意事项”部分和“实施身份验证和授权”部分

    所以我们说你已经完成了这个(或者你发送了每个请求的用户名和密码 - 一个坏主意,但是嘿,这只是出于教育目的)而且你有一个AuthenticateUser方法,如果用户是未经过身份验证。现在,在每个公开的REST方法中,您都可以添加此调用(参数可以是用户名和密码,也可以是身份验证令牌)

    if (!AuthenticateUser(/* auth params here */))
    
    {
    
        WebOperationContext.Current.OutgoingResponse.StatusCode =
    
            HttpStatusCode.Unauthorized;
    
        return;
    }
    

    这会导致请求失败,客户端将收到HTTP 403 Forbiden响应。

    我假设您正在使用HttpWebRequest来调用REST API。

    因此,在您的客户端程序中,在您准备好请求后,添加了您需要的任何参数,请执行此操作

    try
    {
        var wResp = (HttpWebResponse)wReq.GetResponse();
        var wRespStatusCode = wResp.StatusCode;
    }
    catch (WebException we)
    {
        var wRespStatusCode = ((HttpWebResponse)we.Response).StatusCode;
        if( wRespStatusCode == HttpStatusCode. Unauthorized)
        {
           // call to your sign in / login logic here
        } else{
            throw we;
        }
    }
    

    您需要在请求中以某种方式包含身份验证令牌,作为get或post paramater或在标头中。 Post或Get只是将参数添加到请求数据中。标题有点困难,我相信它在MSDN链接中概述了我上面提到的。

答案 1 :(得分:0)

为什么不在您的REST服务中使用OAuth或OpenID?!有OAuth 2.0或以前的版本。还有客户端和服务器的实现。 OAuth适用于REST服务

您无需创建自己的机制。

OAuth的主要网站 - http://oauth.net/code/ 在那里,您可以找到有关OAuth如何工作,流程等的说明。此外,还有指向实施的链接,例如DotnetOpenAuth

最新规范 - http://tools.ietf.org/html/draft-ietf-oauth-v2.

你可以在他们的Github repo上找到很多DotNetOAuth OAuth实现的样本 https://github.com/AArnott/dotnetopenid/tree/master/samples

答案 2 :(得分:-1)

@jbtule和@Damien_The_Unbeliever在使用哈希密码存储盐方面做得非常好。

至于你如何实现它的问题,我不会把它作为一个单独的服务方法,而是让认证的一部分调用本身。然后由客户端通过服务调用传递凭证。

This link 详细介绍了如何实现这一点,从服务器和客户端看起来如何等。

编辑:您可以传递登录令牌,并在执行请求之前检查它是否在Web服务上有效,而不是像上面链接中那样在消息凭据中传递用户名和密码。

答案 3 :(得分:-2)

我最近(过去几周)的方式是通过IDispatchMessageInspector完成的。在消息检查器类中,我使用securityContext.AuthorizationContext.ClaimSets检查客户端(调用者)证书,但您可以使用自定义标头(用户,密码)并查看OperationContext.Current.IncomingMessageHeaders。在AfterReceiveRequest()中,如果用户不是有效用户,我会抛出一个错误,或者只是返回null表示成功。

然后我创建了一个将我的检查器(MessageInspector)添加到服务类的属性:

[AttributeUsage(AttributeTargets.Class)]
public class AuthorizeAttribute : Attribute, IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcherBase dispatcher in serviceHostBase.ChannelDispatchers)
        {
            var channelDispatcher = dispatcher as ChannelDispatcher;
            if (channelDispatcher != null)
            {
                foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
                {
                    var inspector = new MessageInspector();
                    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
                }
            }
        }

        //var config = new ServiceLayerConfiguration();
        //config.RequestProcessorImplementation = typeof(PassThruRequestProcessor);
        //config.Initialize();

    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }
}

最后在服务类中,我只需添加属性。

[AuthorizeAttribute]
public class OperaService : IMyService

如有必要,我可以提供更多细节。我的盒子上还有客户端/服务应用程序。 :)