13

对于我的应用程序,我正在实现与 zentask 中所示相同的安全性。

public class Secured extends Authenticator {

@Override
public String getUsername(Context ctx) {
    return ctx.session().get("email");

}

@Override
public Result onUnauthorized(Context ctx) {
    ctx.flash().put("error", "please login to proceed");
    return redirect(routes.Application.index());
}

}

当用户通过身份验证时 isuser session().put("email", email);

我有两个问题。第一:当用户离开应用程序而不使用注销时,你如何使会话无效?第二个更严重的问题是我使用firefox插件检查了cookie cookies manager+,我可以复制一个cookie然后粘贴它,因此我可以访问方法而无需先登录,基本上我可以窃取会话

4

4 回答 4

40

Play Framework 使用无状态会话。服务器端没有存储状态,而是所有状态都存储在会话 cookie 中。为了验证会话,Play 使用密钥对会话进行签名,并在带有会话 cookie 的请求到达时验证签名。如果用户要篡改会话数据,例如,如果他们将会话中的电子邮件地址更改为其他人的电子邮件地址,则签名将不匹配,因此 Play 将拒绝会话 cookie。

是的,您可以复制 cookie 并稍后使用。但是您不能更改 cookie。这意味着您可以“偷”的唯一 cookie 是您自己的,但从您自己那里窃取并不是真正的窃取。所以不,你不能窃取会话,除非通过利用 XSS 等其他漏洞,但会话令牌也容易受到攻击。

默认情况下,Play 配置为创建“会话”cookie,即在您关闭浏览器时过期的 cookie。因此,如果用户退出浏览器,浏览器将删除所有会话 cookie,用户将不再登录。会话令牌也是如此。

有一个注意事项需要注意,那就是会话令牌也会在服务器上过期,因为服务器保持状态。无状态签名会话,例如 Play 中使用的会话,则不会。但是,您可以自己实现过期机制,方法是在创建会话时将时间戳存储在会话中,并验证该时间戳不早于 getUsername() 方法中配置的过期时间。由于时间戳存储在签名的会话中,在不更改签名的情况下无法篡改时间戳,所以这种简单的机制是相当安全的。一个更高级的解决方案可能是实现一个过滤器,在每次请求进入时更新该时间戳,以便到期可以基于最后一次访问,而不是用户登录的时间。

于 2013-08-06T02:23:35.770 回答
7

您的假设是绝对正确的,您不能按照 Zentask 示例使服务器上的会话无效。尽管会话 cookie 是使用配置文件中的私钥签名的,但相同的值会产生相同的签名 cookie。正如您已经知道的那样,如果有人从用户那里窃取了 cookie,那么用户和您(服务器)都无法阻止小偷“登录”用户的帐户。

现在基本上有两种选择:

  1. 将您已有的关于用户的易失性信息存储在只有您和用户知道并且不时更改的 cookie 中。一个例子是密码哈希的一部分。一旦用户更改密码,该信息将不再有效,所有旧会话 cookie 均无效。这种方法的一个缺点:如果用户不更改存储的信息,cookie 将在很长一段时间内有效,甚至可能永远有效。
  2. 创建服务器端会话管理。为此,您必须有一个数据库、一个键值缓存或类似的东西。在那里,您存储了一个随机生成的(加密安全的)会话密钥、用户名/ID 以及会话将自动失效的日期。您还可以存储 IP 地址以提高防止 cookie 窃取的安全性。然后必须将会话密钥写入 cookie。当用户单击注销按钮时,您使当前会话(或该用户的所有会话)无效。
于 2013-05-12T14:38:20.613 回答
4

将用户 ID 简单地放在 cookie 中根本不安全。正如您所指出的,任何人都可以发明 cookie 值。

会话:相反,您需要在 cookie 中放入任意(例如随机)值,然后在服务器上在映射表中查找用户的身份。该任意值必须经常更改,因此您的登录会话通常会持续 30 分钟。每个登录都提供一个新的任意值,该值称为会话 ID。

失效:在没有任何请求的一段时间(例如 30 分钟)后,通过从查找表(在服务器端)中删除该条目来使会话失效。任何具有不在表中的会话 id 的请求都将被视为未经身份验证的请求,并提示您再次登录。用户是否忘记注销也没关系。

黑客攻击:因为该值是任意的,所以黑客无法提前知道未来的会话 id 是什么。你仍然容易受到会话窃取的影响,但难度要大得多:黑客必须在会话 ID 被使用时才找到它,然后只能在一定时间内使用它。您可以采取一些措施来防止这种情况发生,例如只允许对特定会话的请求来自特定 IP 地址。您还可以快速循环会话 ID,甚至是每个请求,但也有不利的一面。通常,为每次登录提供唯一的会话 ID,尤其是通过 HTTPS 完成时,足以满足大多数身份验证需求。

持久性:如果在任何给定会话期间(例如 30 分钟)内并发用户的数量很少,那么您不一定需要将其放入数据库中。在内存中维护它的开销很低,但缺点是如果循环服务器,所有用户都需要重新登录。如果您确实将会话 ID 放入数据库中,则需要确保在服务器启动时它可以清除所有旧会话。

用户信息:将用户的电子邮件地址放入 cookie 中仍然有价值,但仅用作登录的“默认”用户 ID。这应该只被视为对用户的方便,而不是表明用户已通过身份验证。

于 2013-05-12T14:38:05.470 回答
4
  • 我建议使用一个模块来为您生成会话 ID。在这个模块中,您可以使用一些方法,如 createSessionId() 或其他东西。您在此方法中保留的生成会话 ID 的逻辑。

  • 我会将会话 ID 创建为 (userId + providerId(Facebook/Google-in case of OAuth/UsernamePassword/Any Provider) + current timestamp + UUID) 的组合,并且在创建此会话 ID 后,我将使用某种算法对其进行加密。这会给我会话 ID

  • 这样做的好处是:

    • 虽然生成会话 ID 需要时间,但没有人会理解它。
    • 另一个优点是,您可以随时在 createSessionId() 方法中更改创建会话 ID 的加密逻辑/策略

  • Playframework 中 session 的另一个问题是 session 没有过期:
    • 为了处理这个问题,一旦用户登录,我们可以在会话中存储时间戳,即除了 cookie 之外什么都没有(通过加密可能是?)
    • 现在对于每个请求检查会话中的时间戳。如果时间戳大于 30 分钟,则使会话无效。如果时间戳不大于 30 分钟,则将会话中的时间戳更新为当前时间戳
于 2013-07-21T16:23:22.640 回答