深入学习Cookie

发布时间:2020-02-01编辑:脚本学堂
本文将对Cookie做一个全面的描述与介绍,感兴趣的朋友可以参考下。

Cookie在身份验证中的应用

我想很多人都在Asp.net的开发中使用过Form身份认证。对于一个用户请求, 我们可以在服务端很方便地判断它是不是代表一个已登录用户。
this.labStatus.Text = (Request.IsAuthenticated ? "已登录" : "未登录");

那么,您有没有好奇过:Asp.net是如何识别一个请求是不是一个已登录用户发起的呢?说到这里,我们就要从用户登录说起了。 为了实现登录及Form认证方式,我们需要如下配置:
 

复制代码 代码如下:
<authentication mode="Forms" >
    <forms name="UserStatus"></forms>
</authentication>

接下来,我们需要实现用户登录逻辑。具体实现方式有很多,不过,最终的调用都是差不多的,如下代码所示:
 

复制代码 代码如下:
private void SetLogin()
{
    System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);
}

只要执行了以上代码,我们就可以看到,前面的判断【Request.IsAuthenticated】返回true,最终会显示"已登录"。 为了探寻这个秘密,我们还是来看一下当前页面的Cookie情况。
cookie-6
果然,多出来一个Cookie,名称与我在配置文件中指定的名称相同。我们再来看看如果注销当前登录会是什么样子的:
 

复制代码 代码如下:
private void SetLogout()
{
    System.Web.Security.FormsAuthentication.SignOut();
}

cookie-7看到了吗,名为"UserStatus"的Cookie不见了。此时如果你再去观察【Request.IsAuthenticated】,可以发现它此时返回 false。 或者,您也可以再试一次,登录后,直接删除名为"UserStatus"的Cookie,也能发现登录状态将显示"未登录"。 或许,您还是有点不清楚前面我调用【System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);】后,Asp.net做了些什么, 回答这个问题其实很简单:自己用Reflector.exe去看一下Asp.net的实现吧。
这里为了更让您能信服登录与Cookie有关,我将直接创建一个Cookie看一下 Asp.net能不能认可我创建的Cookie,并认为登录有效。请看代码:
 

复制代码 代码如下:

private void SetLogin()
{
    //System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);

    // 下面的代码和上面的代码在作用上是等效的。
    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
        2, "fish", DateTime.Now, DateTime.Now.AddDays(30d), false, string.Empty);
    string str = FormsAuthentication.Encrypt(ticket);

    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, str);
    Response.Cookies.Add(cookie);
}

如果执行这段代码,您将发现:【Request.IsAuthenticated】返回true,登录状态会显示"已登录"。
至此,我们可以得出一个结论: Form身份认证依赖Cookie,Asp.net就是每次检查我们在配置文件中指定的Cookie名称,并解密这个Cookie来判断当前请求用户的登录状态。

回到顶部
Cookie的安全状况

从以上图片,您应该能发现:浏览器能提供一些界面让用户清楚的观察我们在服务端写的Cookie, 甚至有些浏览器还提供很方便的修改功能。如下图所示:
cookie-8
所以,我们在服务端写代码读取Cookie时,尤其是涉及类型转换、反序列化或者解密时,一定要注意这些操作都有可能会失败。 而且上图也清楚的反映了一个事实:Cookie中的值都是“一目了然”的,任何人都能看到它们。所以,我们尽量不要直接在Cookie中 保存一些重要的或者敏感的内容。如果我们确实需要使用Cookie保存一些重要的内容,但又不希望被他人看懂, 我们可以使用一些加密的方法来保护这些内容。

1. 对于一些重要性不高的内容,我们可以使用Base64之类的简单处理方式来处理。

2. 对于重要性相对高一点的内容,我们可以利用.net提供的一些加密工具类,自己来设计加密方法来保护。不过, 密码学与加密解密并不是很简单的算法,因此,自己设计的加密方式可能不会很安全。

3. 重要的内容,我们可以使用.net提供的FormsAuthenticationTicket,FormsAuthentication来加密。我认为这种方式还是比较安全的。 毕竟前面我们也看过了,Asp.net的Form身份认证就是使用这种方式来加密用户登录的身份标识的,所以,如果这种方式不安全, 也就意味着Asp.net的身份认证也不安全了。 如果您使用这种方式来加密,那么请注意:它产生的加密后文本还是比较大的, 前面我也提到过,每次请求时,浏览器都会带上与请求相匹配的所有Cookie,因此,这种Cookie会对传输性能产生一定的影响, 所以,请小心使用,切记不可过多的使用。

这里要补充一下:去年曾经出现过【Padding Oracle Attack】这个话题, 一些人甚至错误的认为是Asp.net加密方式不安全!如果您也是这样认为的,那么可以看一下这篇文章: 浅谈这次ASP.NET的Padding Oracle Attack相关内容 ,以消除这个错误的认识。当然了,我们也可以从这个话题得到一些收获:解密失败时,不要给出过多的提示,就当没有这个Cookie存在。

回到顶部

如何在C#发请的请求中使用Cookie

前面我们一直在谈服务端与浏览器中使用Cookie,其实浏览器也是一个普通的应用程序,.net framework也提供一些类也能让我们 直接发起HTTP请求,下面我们来看一下如何在C#发请的请求中使用Cookie ,其实也很简单,主要是使用了CookieContainer类,请看以下演示代码:
 

复制代码 代码如下:

private static string SendHttpRequestGet(string url, Encoding encoding,
            CookieContainer cookieContainer)
    {
        if( string.IsNullOrEmpty(url) )
            throw new ArgumentNullException("url");

        if( encoding == null )
            throw new ArgumentNullException("encoding");

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "GET";
        request.CookieContainer = cookieContainer;
       
        using( WebResponse response = request.GetResponse() ) {
            using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) {
                return reader.ReadToEnd();
            }
        }
    }

    private void SendHttpDEMO()
    {
        StringBuilder sb = new StringBuilder();
        CookieContainer cookieContainer = new CookieContainer();

        string url = "http://www.taobao.com";
        SendHttpRequestGet(url, Encoding.Default, cookieContainer);

        // 后面可以继续发起HTTP请求,此时将会包含上次从服务器写入的Cookie
        //SendHttpRequestGet("同域名下的其它URL", Encoding.Default, cookieContainer);

        // 至此,我们可以显示取得了哪些Cookie
        CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));
        if( cookies != null ) {
            foreach( System.Net.Cookie cookie in cookies )
                sb.AppendLine(cookie.ToString());
        }
        txtCookies.Text = sb.ToString();
    }

回到顶部
重构与使用总结

在前面的Asp.net示例代码中,我一直使用.net提供的HttpCookie类来操作Cookie,是为了展示用原始的方式来使用Cookie, 这些代码有点重复,也有点繁琐, 为此,我提供了几个简单的方法可以更容易的使用Cookie,也算是对Cookie使用的一个总结。
 

复制代码 代码如下:

/// <summary>
/// 用于方便使用Cookie的扩展工具类
/// </summary>
public static class CookieExtension
{
    // 我们可以为一些使用频率高的类型写专门的【读取】方法

    /// <summary>
    /// 从一个Cookie中读取字符串值。
    /// </summary>
    /// <param name="cookie"></param>
    /// <returns></returns>
    public static string GetString(this HttpCookie cookie)
    {
        if( cookie == null )
            return null;

        return cookie.Value;
    }

    /// <summary>
    /// 从一个Cookie中读取 Int 值。
    /// </summary>
    /// <param name="cookie"></param>
    /// <param name="defaultVal"></param>
    /// <returns></returns>
    public static int ToInt(this HttpCookie cookie, int defaultVal)
    {
        if( cookie == null )
            return defaultVal;

        return cookie.Value.TryToInt(defaultVal);
    }

    /// <summary>
    /// 从一个Cookie中读取值并转成指定的类型
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cookie"></param>
    /// <returns></returns>
    public static T ConverTo<T>(this HttpCookie cookie)
    {
        if( cookie == null )
            return default(T);

        return (T)Convert.ChangeType(cookie.Value, typeof(T));
    }

    /// <summary>
    /// 从一个Cookie中读取【JSON字符串】值并反序列化成一个对象,用于读取复杂对象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cookie"></param>
    /// <returns></returns>
    public static T FromJson<T>(this HttpCookie cookie)
    {
        if( cookie == null )
            return default(T);

        return cookie.Value.FromJson<T>();
    }

    /// <summary>
    /// 将一个对象写入到Cookie
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="name"></param>
    /// <param name="expries"></param>
    public static void WriteCookie(this object obj, string name, DateTime? expries)
    {
        if( obj == null )
            throw new ArgumentNullException("obj");

        if( string.IsNullOrEmpty(name) )
            throw new ArgumentNullException("name");
       

        HttpCookie cookie = new HttpCookie(name, obj.ToString());

        if( expries.HasValue )
            cookie.Expires = expries.Value;

        HttpContext.Current.Response.Cookies.Add(cookie);
    }

    /// <summary>
    /// 删除指定的Cookie
    /// </summary>
    /// <param name="name"></param>
    public static void DeleteCookie(string name)
    {
        if( string.IsNullOrEmpty(name) )
            throw new ArgumentNullException("name");

        HttpCookie cookie = new HttpCookie(name);

        // 删除Cookie,其实就是设置一个【过期的日期】
        cookie.Expires = new DateTime(1900, 1, 1);
        HttpContext.Current.Response.Cookies.Add(cookie);
    }
}
 

更完整的代码可以从本文的示例代码中获得。

使用方式:
 

复制代码 代码如下:

public static class TestClass
{
    public static void Write()
    {
        string str = "中国";
        int aa = 25;
        DisplaySettings setting = new DisplaySettings { Style = 3, Size = 50 };
        DateTime dt = new DateTime(2012, 1, 1, 12, 0, 0);

        str.WriteCookie("Key1", DateTime.Now.AddDays(1d));
        aa.WriteCookie("Key2", null);
        setting.ToJson().WriteCookie("Key3", null);
        dt.WriteCookie("Key4", null);
    }

    public static void Read()
    {
        HttpRequest request = HttpContext.Current.Request;

        string str = request.Cookies["Key1"].GetString();
        int num = request.Cookies["Key2"].ToInt(0);
        DisplaySettings setting = request.Cookies["Key3"].FromJson<DisplaySettings>();
        DateTime dt = request.Cookies["Key4"].ConverTo<DateTime>();
    }   
}

注意哦:以上代码中都是直接使用字符串"Key"的形式,这种方式对于大一些的程序在后期可能会影响维护。
所以建议:将访问Cookie所使用的Key能有一个类来统一的定义,或者将读写操作包装成一些属性放在一个类中统一的管理。
 

复制代码 代码如下:

public static class CookieValues
{
    // 建议把Cookie相关的参数放在一起,提供 get / set 属性(或者方法)来访问,以避免"key"到处乱写

    public static string AAA
    {
        get { return HttpContext.Current.Request.Cookies["Key1"].GetString(); }
    }
    public static int BBB
    {
        get { return HttpContext.Current.Request.Cookies["Key2"].ToInt(0); }
    }
    public static DisplaySettings CCC
    {
        get { return HttpContext.Current.Request.Cookies["Key3"].FromJson<DisplaySettings>(); }
    }
    public static DateTime DDD
    {
        get { return HttpContext.Current.Request.Cookies["Key4"].ConverTo<DateTime>(); }
    }
}

回到顶部
补充

根据一些朋友提供的反馈,这里再补充4个需要注意的地方:
1. 如果使用Form登录验证且希望使用Cookie方式时,建议设置 cookieless="UseCookies", 因为这个参数的默认值是:cookieless="UsedeviceProfile",Asp.net可能会误判。 看这里,看这里哦。
 

复制代码 代码如下:
<authentication mode="Forms" >
    <forms name="MyCookieName" cookieless="UseCookies"></forms>
</authentication>

2. Cookie有3个属性,一般我们可以不用设置,但它们的值可以在Web.config中指定默认值:
 

复制代码 代码如下:
<httpCookies domain="www.123.com" httpOnlyCookies="true" requireSSL="false"/>

3. 虽然在写Cookie时,我们可以设置name, value之外的其它属性,但是在读取时,是读不到这些设置的。 其实在我的示例代码中有体现,我前面也忘记了说明了。

4. HttpRequest.Cookies 与 HttpResponse.Cookies 会有关系(很奇怪吧)。
以下代码演示了这个现象:
 

复制代码 代码如下:

protected void Page_Load(object sender, EventArgs e)
{
    DateTime.Now.ToString().WriteCookie("t1", null);

    label1.Text = ShowAllCookies();

    Guid.NewGuid().ToString().WriteCookie("t2", null);

    // 如果去掉下面代码,将会看到2个t1
    Response.Cookies.Remove("t1");
    Response.Cookies.Remove("t2");
}

private string ShowAllCookies()
{
    StringBuilder sb = new StringBuilder();

    for( int i = 0; i < Request.Cookies.Count; i++ ) {
        HttpCookie cookie = Request.Cookies[i];
        sb.AppendFormat("{0}={1}<br />", cookie.Name, cookie.Value);
    }

    return sb.ToString();
}

上面的试验代码将会一直显示 t1 的Cookie ,这里就不再贴图了。
点这里下载本文示例代码
原文链接:http://www.cnblogs.com/fish-li/archive/2011/07/03/2096903.html