V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zeacev
V2EX  ›  PHP

PHP 登录中 Session 和 Cookie 问题...

  •  2
     
  •   zeacev · 2015-08-16 18:14:37 +08:00 · 5059 次点击
    这是一个创建于 3176 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Web新手,最近想写一个完整的博客系统练练手,登录认证这一块有些地方思路不是很清晰,请教一下大家。

    首先,单独的Session和Cookie我思路很清晰,但是两者结合再加上数据库后,思路就不怎么清晰了。出于安全考虑,各位在开发的时候,在本地Cookie会放一些什么内容?本地的Cookie内容是怎么和服务器上的Session打交道?希望各位不吝赐教。

    然后,有没有必要在user表中增加一个字段比如说叫做AuthCode,再浏览器POST过来的username和password去数据库对比成功之后,将这个AuthCode写到Cookie去,下次浏览器发送请求的时候直接对比username和AuthCode就行了?

    其次,我看了WordPress和Typecho的数据库表结构,里面没有类似于AuthCode的字段,但是登录之后却会有这么一个类似于AuthCode的Cookie值,比如说下图Typecho的:
    Typecho Cookie AythCode

    网上那些教程都只简单的说了下到本地设置个Cookie就行了,这样真的安全?

    处理登录的时候,各位是怎么在程序中实现的?能否说一下大致思路?

    感谢各位的解答

    39 条回复    2015-10-21 03:49:40 +08:00
    kslr
        1
    kslr  
       2015-08-16 18:18:17 +08:00   ❤️ 1
    没必要用Cookie,用Session就好了
    oott123
        2
    oott123  
       2015-08-16 18:26:19 +08:00   ❤️ 1
    设置了 cookie 之后签个名
    lyragosa
        3
    lyragosa  
       2015-08-16 18:31:25 +08:00   ❤️ 1
    不涉及多设备登录,交叉登录,登出一台设备自动登出所有设备等复杂的安全性措施的话。

    可以完全不使用session,代价是每个页面都要重新请求一次数据库读出用户信息,然后比对客户端的cookie

    cookie字串肯定要加密,简单的用几个hash加密加点盐再加点注册时间啊随机字符串就行。

    这样做的好处就是程序结构非常简单清晰易读,也不涉及session什么的。缺点就是开销稍微大了一丢丢,不过无所谓啦。
    zeacev
        4
    zeacev  
    OP
       2015-08-16 19:11:28 +08:00
    @lyragosa

    @kslr 你们两位结论正好相反,能否详细说说你的思路?
    imink
        5
    imink  
       2015-08-16 19:32:55 +08:00   ❤️ 1
    可以看下php文档里面的start_session() 方法(没记错的话),每次用户登录的时候session后台开启,保存用户的authCode(可以根据uid还有password加密),以确保如果要浏览需要验证的页面(比如修改个人主页,编辑日志)的时候拥有权限。至于cookie,由于存在本地,可以定时删除,一般的应用情景是,记录用户上次登录的状态(“勾选以便下次直接登录”),或者用户个人网站偏好。

    你图片上的cookie,有可能是为了“勾选以便下次登录”而设置的。
    我平常用的最多的还是操作session。
    raincious
        6
    raincious  
       2015-08-16 19:46:09 +08:00   ❤️ 1
    用 start_session() 启动会话之后,PHP 会自动帮你处理会话数据,包括向Client发Cookie(而不用你自己去发),然后将存在服务器上的用户数据填充给 $_SESSION。整个操作你只需要调用 start_session、剩下的 PHP 会帮你处理好,你只需要读写 $_SESSION,就是这么简单。

    而 PHPSESSION 那个 Cookie 只是 PHP 自己的 Session 机制设定的一个 Session ID 而已。
    incompatible
        7
    incompatible  
       2015-08-16 19:50:09 +08:00 via iPhone   ❤️ 1
    @lyragosa 每个请求都读数据库?
    没有一个web开发的内行会这么干
    rogeecn
        8
    rogeecn  
       2015-08-16 21:22:29 +08:00   ❤️ 1
    都没给撸说说SESSIONID是通过cookie带过去的么?就像你登陆了再清空COOKIE肯定要被退出管理权限呀。
    Keita1314
        9
    Keita1314  
       2015-08-16 21:46:36 +08:00   ❤️ 1
    第一次登陆之后,服务器的session保存当前会话信息,然后在数据库的一张表保存sessionID与用户名,同时将sessionID写入浏览器的cookie。
    再次登陆,服务端通过浏览器的cookie中取得sessionID,然后到数据库中根据sessioID判断出当前登陆用户。
    大概流程就是这样子。
    lyragosa
        10
    lyragosa  
       2015-08-16 21:52:55 +08:00   ❤️ 1
    @incompatible 说了啊 肯定有坏处的,会有额外的开销。
    但是如果是小型的产品的话,与其花时间折腾为了节省这点性能不如做与业务有关的事情。

    换句话说就是,如果产品只有100个人用,就不要折腾优化十万并发请求的事情。
    zeacev
        11
    zeacev  
    OP
       2015-08-16 22:00:03 +08:00
    @rogeecn 这个我知道
    incompatible
        12
    incompatible  
       2015-08-16 23:07:07 +08:00   ❤️ 1
    @lyragosa 6楼已给出php中集成session的成熟方案
    我比较熟悉的java servlet规范中也原生预置了类似的session方案
    我没看出来这些开箱可用的方案哪里“折腾”了,反而你的方案还要写额外的代码,这才是真的折腾
    ferock
        13
    ferock  
       2015-08-17 00:04:21 +08:00 via Android   ❤️ 1
    都是一样的东西,不走ssl 都不安全
    msg7086
        14
    msg7086  
       2015-08-17 02:48:36 +08:00   ❤️ 1
    #7 @incompatible 每个请求去读数据库有什么问题?又不会影响什么速度。
    incompatible
        15
    incompatible  
       2015-08-17 04:10:20 +08:00   ❤️ 1
    @msg7086
    当然会影响速度 读取数据库要耗时间在应用服务器与数据库服务器间的网络IO、要耗数据库的CPU

    #3的最大问题不在于每个请求读数据库会影响速度,而是在于业内没人这么干。
    如我12楼所说,PHP和Java中都有成熟的方案,非常简单,根本不需要造一个毫无必要的轮子。
    xuhaoyangx
        16
    xuhaoyangx  
       2015-08-17 04:14:24 +08:00   ❤️ 1
    @incompatible 赞同
    kanezeng
        17
    kanezeng  
       2015-08-17 09:01:57 +08:00   ❤️ 1
    @incompatible 单服务器这样肯定没问题,不过这样的话session信息其实是保存在服务器内存本身吧。如果服务器扩展到两台甚至,有可能存在一种情况是用户的不同请求被分配到不同的服务器上,那么如果请求被分配到内存中没有session信息的那些服务器上,用户就又变成未登录状态了吧。
    所以如果脱离单机环境的话,这种问题还是值得讨论一下的吧?
    haiyang416
        18
    haiyang416  
       2015-08-17 10:25:57 +08:00   ❤️ 1
    博客系统的登录状态一般来说需要保存特定时间长度,不推荐直接使用 session 来储存,因为你对其运行机制不熟悉,会遇到很多意外情况。
    作为练手项目,我们可以直接使用 cookie 来保存登录状态。
    比如你可以将用户 id 存入 cookie ,在每次打开页面都检查 $_COOKIE["uid"] 是否设置来判断用户是否登录。
    然而只依靠一个 uid 来鉴权是非常危险的事情,因为 cookie 是可以伪造的,所以我们需要在 uid 以外另添加一个 cookie 来杜绝伪造。
    这个 cookie 通常是一个 hash 值,比如就将它取名为 AuthCode ,它的值是可以是 hash ("sha256", uid + salt ) 或者其他类似的字符串。
    那么在每次打开页面的时候就需要判断两种情况:是否设置了 $_COOKIE["uid"]; hash ("sha256", $_COOKIE["uid"] . "IAmSalt!") 是否和 $_COOKIE["authCode"] 相同;
    那么只要 hash 算法或者 salt 不泄漏,就可以杜绝大部分伪造,一下就感觉安全了很多是不是?
    当然,这只能应付“最最最”基本的情况,比如我的 cookie 被拦截了,这样的鉴权方式就算改密码都没用,怎么办?
    只能靠楼主自己去了解相关的解决方式了。
    incompatible
        19
    incompatible  
       2015-08-17 10:56:48 +08:00   ❤️ 2
    @kanezeng
    集群环境下通常采用这两种方案之一:
    - 在 load balancer 上做 sticky session ,把来自某个客户端的请求永远转发至相同的应用服务器
    - 把 session 信息放在 redis/memcached 等支持高并发的缓存中
    数据库虽然也可以做集中式存储,但是在高并发情况下性能会急剧下降,所以没人用它存 session
    kanezeng
        20
    kanezeng  
       2015-08-17 12:23:49 +08:00   ❤️ 1
    @incompatible 赞同,前面回复的时候我脑子里想的也是 redis , redis 的特性应该是最适合这种情况的吧。
    zeacev
        21
    zeacev  
    OP
       2015-08-17 13:40:31 +08:00
    @ferock 这里说的安全不是传输安全,
    abcfyk
        22
    abcfyk  
       2015-08-17 16:17:08 +08:00
    @haiyang416 我晕。。对 LZ 这种新手来说,这个明显比 session 难多了好吧。。

    对新手来说, session 可以简单认为就是安全的, cookie 是不安全的。存取 session 只需要非常简单一行代码就搞定。这还不够简单还要取 uid ,加盐, hash 。。。 LZ 会晕的。
    abcfyk
        23
    abcfyk  
       2015-08-17 16:29:35 +08:00   ❤️ 1
    我的一些见解。希望能帮到你。

    Q :出于安全考虑,各位在开发的时候,在本地 Cookie 会放一些什么内容?
    A : cookie 通常用于保存非敏感信息。比如 userId ,记住密码的开关,比如 authCode 也是这种

    Q :本地的 Cookie 内容是怎么和服务器上的 Session 打交道?
    A :注意看 phpsessid 那个东西,这个可以认为是服务器上随机生成的一个随机数,对用户 /浏览器 /浏览进程唯一,每次浏览器请求服务器的时候会说:“哎,你给我 xx 页面的东西。我的 sessionId 是 xx ”。然后服务器上跑的 PHP 可以通过 nginx/apache 拿到这个 sessionId ,也就知道你是谁了。你的身份就对应上了。

    Q :然后,有没有必要在 user 表中增加一个字段比如说叫做 AuthCode ,再浏览器 POST 过来的 username 和 password 去数据库对比成功之后,将这个 AuthCode 写到 Cookie 去,下次浏览器发送请求的时候直接对比 username 和 AuthCode 就行了?
    A :没有必要

    Q :其次,我看了 WordPress 和 Typecho 的数据库表结构,里面没有类似于 AuthCode 的字段,但是登录之后却会有这么一个类似于 AuthCode 的 Cookie 值。
    A :我一般会用 AuthCode 做记住密码的开关或者权限等级等一些信息。其他 PHPer 我不清楚。。

    Q :网上那些教程都只简单的说了下到本地设置个 Cookie 就行了,这样真的安全?
    A :不是很明白这个说法。本地设置 cookie 是设置什么?名称是什么?值是什么? Web 开发一个原则就是永远不要相信用户的输入,所有保存在客户端的信息都是非常不安全的。

    Q :处理登录的时候,各位是怎么在程序中实现的?能否说一下大致思路?
    A : 额。。太长了。有空再写。


    A :
    haiyang416
        24
    haiyang416  
       2015-08-17 17:11:36 +08:00   ❤️ 1
    @abcfyk session 保存登录状态,浏览器关闭就没了,每次打开浏览器都重新登录?设置 cookie 的保存时间会比设置 session 的生命周期更难? 什么都保存在 session 里,只靠 session id 裸奔,真的会比 cookie 安全?
    jugelizi
        25
    jugelizi  
       2015-08-17 21:42:22 +08:00   ❤️ 1
    @abcfyk 不要误导人
    session 是依赖 cookie 的 cookie 做的不好 session 没有任何安全可言
    网上加密方法很多, userid 和 IP 以及特定的 key 生成 cookie 服务端可以解开基本就认为安全的
    incompatible
        26
    incompatible  
       2015-08-18 01:27:41 +08:00   ❤️ 1
    @haiyang416
    @jugelizi

    cookie 与 session 跟 安全 是八竿子打不着的关系
    简单来说 cookie 是保存在浏览器中的客户信息 session 是保存在服务器端的客户信息

    cookie 与 session 唯一的联系就是 一些 Web 应用容器是通过在 cookie 中设置 sessionId 的方式来实现客户与 session 的绑定的(在浏览器不支持 cookie 的情况,比如 Java 的 servlet 容器会通过重写 url 、在所有 url 后加上 jsessionid 的方式来实现客户与 session 的绑定)

    至于‘登录后记住我’这种功能,其实就是一个鉴权的过程。首次登录时浏览器中的网页发送用户名和密码给服务器端,服务器完成鉴权后,发给客户一个凭证,此后的客户的访问,带着凭证来证明合法身份即可。
    这个凭证理论上来说你可以选择放在 cookie 中,也可以选择放在 session 中,两种方式的安全性是一致的。但实际上如果放在 cookie 中,服务器端依然要护凭证和 cookie 的对应关系及过期时间,还不如干脆放在 session 中来得省事儿。


    以上过程中有两个安全弱点。 一是鉴权过程中用户名和密码会被截获(使用 https 可破)。 二是完成鉴权后的访问中, cookie 被截获(使用 https 可破)或者被冒用(请不要把钥匙交给别人)。 显然这都不是 cookie 或 session 可以解决的问题。所以说‘ cookie 跟 session ’与‘安全‘没有关系。


    至于把 userId+IP+特定 key 加密再到服务器端解,这是非常不靠谱的。我若是破解了你的加密算法,岂不是我可以想登录哪个用户就登录哪个用户?
    haiyang416
        27
    haiyang416  
       2015-08-18 04:33:41 +08:00 via Android   ❤️ 1
    @incompatible 请只用 session 实现「记住我」一个月的功能,并且比用 cookie 更适合新人博客练手。
    另外,算法不是你想破解就破解,比如 uid 为 1 的 hash 为 dc0afe3bee4310343511b13f2cd8ac19c8ddfaf9572c13e02c44bddf8722b722 ,算法为 sha256 (uid + salt ),请“破解” uid 为 2 用户的 hash 。
    haiyang416
        28
    haiyang416  
       2015-08-18 05:04:48 +08:00 via Android   ❤️ 1
    对此帖的回复到此为止。
    楼主可以鉴别和选择对自己有用的信息,能解惑最好。
    haiyang416
        29
    haiyang416  
       2015-08-18 05:54:29 +08:00 via Android   ❤️ 1
    忘记贴一个网址了: https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#title.2
    内容包含登录相关所有基本操作,建议一读。
    jugelizi
        30
    jugelizi  
       2015-08-18 08:44:43 +08:00   ❤️ 1
    想了下还是回复下 在 php 的 session 中默认使用的文件管理,而且过期删除机制的随机性,我不认为在大中型项目中使用 session 来做用户凭证,要么 cookie 走加密,参见 discuz ,或者用 redis 等实现 session 存储,默认文件读写的导致的锁是会有问题的。
    incompatible
        31
    incompatible  
       2015-08-18 10:53:42 +08:00   ❤️ 1
    @haiyang416 为什么要” 请只用 session 实现「记住我」一个月的功能“?就因为你贴那个英文文章里建议这么做(并且没有解释原因)?

    关于算法破解,你赢了,我个人破解不了 uid 为 2 的 hash😂
    但是破解这个难道不是时间问题?
    haiyang416
        32
    haiyang416  
       2015-08-18 11:08:12 +08:00   ❤️ 1
    @incompatible 最后回复一次,我给你一个理由,请试想这样一个情形:
    楼主写好了自己的博客系统,登录状态保存在 session 里,楼主听着歌儿,打开博客编辑器,愉快的写着文章。
    一篇很好的文章花了楼主 45 分钟时间,完成后点击了发布按钮,然后,就没有然后了。
    由于楼主写文章过于专注, 45 分钟内没有刷新或者访问自己的博客,等到点击了发布按钮后才发现自己被跳转到了登录界面,由于楼主没有事先保存文章,所以写好的文章就这么丢失了,楼主怒了,我明明登录了,为什么为什么为什么现在又要我登录?
    devion
        33
    devion  
       2015-08-18 11:22:52 +08:00   ❤️ 1
    原来是这样的!
    incompatible
        34
    incompatible  
       2015-08-18 13:11:14 +08:00   ❤️ 1
    @haiyang416 难道“只用 session 实现「记住我」一个月的功能”就能杜绝这个问题了?😢

    另外,为什么 45 分钟楼主没有刷新 /访问自己的博客 session 就没了? 以及 #24 “ session 保存登录状态,浏览器关闭就没了,每次打开浏览器都重新登录”这又是为什么? 你确定你足够懂 session 的运行机制?
    haiyang416
        35
    haiyang416  
       2015-08-18 13:20:03 +08:00   ❤️ 1
    @incompatible 你真的会 PHP ?
    elvba
        36
    elvba  
       2015-08-18 23:04:55 +08:00
    @haiyang416 很明显他是会的,我也想知道为什么 45 分钟没有刷新 /访问自己的博客 session 就没了,这个 45 分钟时怎么来的?另外 “ session 保存登录状态,浏览器关闭就没了”,你自己登录 V2EX 关闭浏览器后就要重新登录了么?
    haiyang416
        37
    haiyang416  
       2015-08-19 08:29:36 +08:00 via Android
    @elvba 这个帖子里讨论的是新手练习写博客系统,所以我在第一次回复中就说了, session 的运行机制要比 cookie 复杂,不建议新手深究。至于 45 分钟,是随口说的,在 PHP 中原生 session 生命周期只有大约 24 分钟,并不是只设置携带 session id 的 cookie 的过期时间可以解决的,在服务器中同样可能被 GC 。所以就避免不了要设置服务器 session 的生命周期,就会牵扯到 php. ini 中 session 相关的设置,比如生命周期,最大生命周期,回收概率计算,保存位置等等。并且这个设置是全局性的,所以会影响到整个博客系统甚至同服务器其他 PHP 程序。当然这些设置可以在 PHP 脚本中设置,但是需要保证每次启用 session 时都必须设置,不然仍然可能被 GC 。当然,有些人会先将楼主设定到 PHP 程序员的水平,认为这些设置都是非常简单的,或者可以放弃原生 session 使用其他储存方式,并且部署 SSL 来保证 session id 不被截取。
    反倒是前面回复中提到的每次打开网页都查询数据库和使用 cookie 的方式被人不耻,其实楼主提到的 typecho 就是这么做的, WordPress 中是又怎么做的 ?前面某位,他的作品有几万人玩,我也玩了一年多了,但是也被说成外行,没办法。
    gdtv
        38
    gdtv  
       2015-10-20 20:24:25 +08:00
    @haiyang416
    @incompatible

    抱歉我挖坟了,因为我最近在做用户登录模块。
    在 PHP 里,只用 session 应该是实现不了“记住密码”功能的,@haiyang416 说得没错,如果要实现这个功能只能借助 cookie ,那又涉及 cookie 的加密了,那就回到了 @haiyang416 的观点“要同时处理 session 和 cookie ”,那我何不一开始就只处理 cookie 呢?
    我比较赞同 @haiyang416

    还是希望 @incompatible 能说一下只用 session 实现记住密码的功能,如果能实现,那比用 cookie 简单方便多了。

    PS: phpmyadmin 登录后 24 分钟不操作就会被自动注销登录,就是因为 session 有生存期,并不是只有关闭浏览器才失效。
    incompatible
        39
    incompatible  
       2015-10-21 03:49:40 +08:00 via iPhone
    @gdtv 抱歉此中我的回帖在 36 楼就终结了 因为我真的不会 php😂 所有关于 cookie 和 session 的机制我都是从 http 权威指南以及 java servlet 规范中习得的

    如果 php 真的有 24 分钟后 session 就被 gc 这样的诡异机制,那它绝对妄称 V2EX 上“最好的编程语言”

    但是请相信我,没有比使用 session 实现“登录后记住我”更正统的方式。你需要的就是在服务器端维持这个 session 足够长的有效性,以及在浏览器端维护一个与该 session 对应的 cookie 。

    在数十万并发的 java web 系统的中,我通过把 session 放在 redis 中的方式来实现应用服务器的 scale out 以及 session 的有效性。在 php 中,请按照 37 楼的提示对 php 进行配置。总之一个只有 24 分钟生命的 session 绝对是不正常的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2871 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 07:18 · PVG 15:18 · LAX 00:18 · JFK 03:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.