【问题标题】:Struggling with authentication method [closed]努力使用身份验证方法[关闭]
【发布时间】:2012-04-22 09:00:40
【问题描述】:

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

  1. 向 RESTful 服务验证客户端
  2. 在客户端的 API 调用中处理身份验证失败

这是一个教学练习:我意识到有内置的身份验证方法,我想从头开始这样做以了解它是如何工作的。

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

背景

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

到目前为止,我已经成功地创建了密码、salt 的哈希值并存储了 salt,并且我已经成功地对用户进行了身份验证。但是,我不确定您将如何封装我所有的 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;
        }
    }

所以我不确定如果以上提到的任何其他内容必须携带到主应用程序,以便主应用程序访问这些休息请求。

【问题讨论】:

  • 理想情况下,存储 3 个值 - 无论您将它们组合成一个二进制 blob,还是单独建模它们,您都需要它们 - a) 密码代码的版本,b) 密码盐,以及c) 密码哈希。存储 (a) 以便以后如果您需要切换到不同的算法,您可以并且可以知道您升级了哪些用户。存储 (b) 和 (c) 以便您可以执行身份验证。
  • 为什么不使用已经内置了所有这些功能的 ASP.NET Membership? (msdn.microsoft.com/en-us/library/yh26yfzy.aspx)
  • 我觉得我必须指出 Eric Lippert 关于编写自己的身份验证模块的建议:“让我给你所有关于滚动你自己的加密算法和安全系统的标准警告:不要。创建几乎安全但不太安全的安全系统非常非常容易。一个给你错误安全感的安全系统比根本没有安全系统更糟糕!”
  • Adam 承认您所说的非常正确,但请注意,出于学习目的,了解您在实施这些方法的内容和方式是一种很好的做法。
  • 如果您希望服务安全地将用户/密码传递给服务,那么您需要使用 SSL。否则,您不妨只使用纯文本用户/密码。

标签: c# web-services rest authentication encryption


【解决方案1】:

所以通常这样做的方式是

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

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

关于如何在 WCF here 中执行此操作进行了相当长的讨论。请参阅“安全注意事项”部分和“实施身份验证和授权”部分

假设您已经完成了此操作(或者您在每次请求时都发送了用户名和密码——这是个坏主意,但嘿,这只是为了教育目的)并且您有一个 AuthenticateUser 方法,如果用户是,则返回 false未认证。现在在每个公开的 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 参数还是在标头中。发布或获取只是将参数添加到请求数据的问题。标题有点困难,我相信它在我上面引用的 MSDN 链接中概述。

【讨论】:

  • 这是一个很好的解释,我只是希望你已经为它合并了我的示例代码。我也想知道为什么它收到了两次反对票,这让我觉得这不是正确的方法?
  • 这里几乎所有的东西都收到了两次反对票,没有任何解释,这让我很怀疑。我正在查看他们的时间安排,我唯一能想到的是人们正在互相投票以试图获得赏金。阅读 MSDN 文章,它应该证实了我所写的大部分内容。虽然它相当长,所以也许只是阅读引用的部分。
  • @JungleBoogie 嘿,你在这方面得到了赏金。你有没有可能以某种方式奖励它。不知道你是否可以在问题关闭的情况下做到这一点,但你可以再做一个,然后给我链接或其他东西
【解决方案2】:

为什么不为您的 REST 服务使用 OAuth 或 OpenID?!有 OAuth 2.0 或更早版本。也有客户端和服务器的实现。 OAuth 通过适用于 REST 服务

您不需要创建自己的机制。

OAuth 的主站点 - http://oauth.net/code/ 在那里你可以找到关于 OAuth 工作原理、流程等的描述。还有实现的链接,例如DotnetOpenAuth

最新规范 - https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2.

您可以在他们的 Github 存储库上找到很多 DotNetOAuth 的 OAuth 实现示例 https://github.com/AArnott/dotnetopenid/tree/master/samples

【讨论】:

    【解决方案3】:

    @jbtule 和 @Damien_The_Unbeliever 在使用散列密码存储盐方面提出了很好的观点。

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

    This link 非常详细地描述了如何实现这一点,从服务器和客户端看它是什么样子的等等。

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

    【讨论】:

    • 请解释降级。我给出了关于如何处理和完成 Web 服务身份验证的有效答案。提供的链接列出了流程每个步骤的代码。
    【解决方案4】:

    我最近(过去几周)的方式是通过 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
    

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

    【讨论】:

    • 为什么投反对票?这确实有效。
    猜你喜欢
    • 1970-01-01
    • 2018-07-05
    • 1970-01-01
    • 1970-01-01
    • 2021-03-01
    • 2010-10-24
    • 2012-01-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多