【问题标题】:Play framework 2: Passing java objects between controllersPlay framework 2:在控制器之间传递java对象
【发布时间】:2013-03-16 16:13:03
【问题描述】:

我有一个 java User 对象,我想将它从我的登录控制器传递到我的仪表板控制器。我读了一堆,我正在尝试以下实现

public static Result authenticate(){
    Form<LoginForm> loginform = Form.form(LoginForm.class).bindFromRequest();
    if(loginform.hasErrors()){
        return badRequest(login.render(loginform));
    }
    else{
        session().clear();
        AppUser user = AppUser.getUserByEmail(loginform.get().email);
        Context.current().args.put("user", user);
        return redirect(routes.DashBoard.index());
    }

和我的仪表板控制器

public static Result index(){
    AppUser user = (AppUser) Context.current().args.get("user");
    return ok(views.html.dashboard.index.render(user));
}

这会导致空指针异常

这是因为这两个请求当然不是同一个请求。

如何以一种友好的方式解决这个问题 顺便说一句,我在文档中看到了一些关于动作组合的东西 但我没看懂。

【问题讨论】:

    标签: playframework playframework-2.0


    【解决方案1】:

    虽然我同意 Mauno 的回答,但我想补充一点,可以在游戏中使用 cache API 来完成您的要求。

    要记住的一点是,多个用户的缓存是相同的。如果您希望能够从缓存中访问特定的用户对象,那么您将必须拥有一个唯一的 ID。您还将在请求之间存储该 ID。玩游戏是一种简单的方法。

    这是一个例子

    public static Result authenticate(){
        Form<LoginForm> loginform = Form.form(LoginForm.class).bindFromRequest();
        if(loginform.hasErrors()){
            return badRequest(login.render(loginform));
        }
        else{
            session().clear();
            AppUser user = AppUser.getUserByEmail(loginform.get().email);
    
    
            String uuid= java.util.UUID.randomUUID().toString();
            Cache.set(uuid,user);
            //store uuid in session for extracting the proper user from cache later
            session("uuid",uuid); 
    
            return redirect(routes.DashBoard.index());
        }
    
    
    public static Result index(){
        //gather uuid stored in session (cookies)
        String uuid = session("uuid") 
        AppUser user = Cache.get(uuid);
    
        return ok(views.html.dashboard.index.render(user));
    }
    

    此外,这些对象不会有可预测的寿命,因此您必须确保检查它们的存在并可能重新创建对象。

    edit:我不确定最后一条语句,在 playframework 1 中,如果缓存中的对象有一段时间不使用,它们将被删除,但是 appears from this post 如果你不这样做'没有在 playframework 2 中指定超时,默认情况下它们可能会无限期地持续存在。这实际上取决于与 play api 一起使用的配置/模块。

    对于您所描述的情况,我不会推荐此解决方案。数据库是存储这些信息的合理位置,您应该只检索每个请求的用户数据。

    只是想说缓存对象是可能的。

    【讨论】:

    • 恕我直言,在这种情况下,缓存 API 或 DB 存储是最好的选择,无论如何,他需要用一些唯一的密钥保存每个对象(并且需要在会话范围内传递密钥),否则所有用户都会被识别为创建缓存条目的第一人。
    • 你说得对,我试着解释一下,然后只是链接到文档
    • @biesior 是正确的缓存 API 不利于安全实现,重复的数据库访问是我试图阻止的。也不建议在会话中存储用户名,因为可以发出虚假请求。我正在考虑使用一个带有 MAP 的类,就像原始 HTTPSession 对象中的那个一样,这很好用,我的一个问题是当会话过期时从该映射中删除条目 - 需要像在 HttpSessionListener 接口中那样实现会话侦听器跨度>
    • ^只是好奇为什么在你实际上不使用它提供的非常关键的方面时选择像 play 这样的框架? :) 我还没有遇到任何真正的讨论,这些讨论表明播放安全登录是以“坏”的方式实现的?我也曾经将用户名或类似的密钥存储到与其他框架的会话中,imo:当细节不是这样时,我不认为这是一种安全风险:) 我觉得你正在尝试优化或解决已经有解决方案的问题.
    • @naoru 来自文档:“当然,cookie 值是使用密钥签名的,因此客户端无法修改 cookie 数据(否则它将失效)。”我不认为您对虚假请求的担忧是有道理的。会话是在缓存中存储项目 ID 的好地方。如果您真的对重复访问数据库有性能问题,那么缓存可能是您的解决方案。但是,不这样做会更简单,而且无论哪种方式,它都可能表现良好。如果您将用户的收集抽象化,则可以在稍后进行性能测试后添加缓存。
    【解决方案2】:

    注意:由于您的问题一般是关于“在动作之间传递对象”,Mike 的回答最适合您的需求,结论是:使用缓存和/或准备充分的 SQL 语句(意味着 getiing目前尽可能少的数据,没有额外的关系等)

    我认为这是“接受答案”的最佳人选。

    另一方面,从讨论中可以清楚地看出,您是在 auth 的上下文中询问的,那是我的 2cc:

    • Mauno 的回答描述了典型的身份验证方法。是的,它需要额外的查询,但是如果您将在嵌入模式下使用示例 H2 数据库,它应该不是一个大问题 - 它很快。此外,您还可以使用缓存 API 稍微优化一下。
    • 实际上存在这种方法存在一些安全漏洞...虽然会话 cookie 是签名的,所以理论上它们不能被操纵...这并不能改变事实,它们可以只是被其他人劫持和重用。最简单的解决方案是在服务器端保存会话 - 在数据库或缓存中,因此您可以比较更多详细信息,如 IP、指纹等。最重要的是,您还可以在注销或一段时间不活动后使整个服务器端会话无效,所以即使攻击者会抢到会话,他也不能再重用它了。当然,这种方法会创建额外的数据库查询,您需要自己选择什么对您更重要,安全性或性能:)
    • 已准备好使用授权/身份验证堆栈:Play-Authenticate,被劫持的可能性警告我已经在其上创建了一个示例,以演示如何使用缓存和/或 DB 添加会话处理:https://github.com/biesior/play-authenticate/commits/2.0.4_sessions/samples/java/play-authenticate-usage - 检查我最近提交的 cmets 和代码差异。

    【讨论】:

    • 我一直认为应该将 db 调用减少到最低限度。我认为您建议的最佳解决方案是快速查询(通常使用主键)进行另一个数据库调用和更昂贵的调用(在我的情况下是发票详细信息)进行一次调用并使用缓存机制。这是一个富有成效的借记谢谢
    【解决方案3】:

    你不应该这样做(甚至不需要这样做),这实际上是我在开始使用 play 时也在考虑的事情 - 然后我意识到我没有使用框架来使用它。

    动作组合实际上是用来保护某些方法或控制器被访问的东西。 (尽管您也可以将它用于其他事情)。 - 但在实现登录时需要它。

    而不是传递您的User 对象.. 您的用户email(用户名)被放入会话中,以后无法在您的控制器端和模板中检索它。 (任何控制器或模板。)

    public static Result authenticate() {
        Form<Login> loginForm = form(Login.class).bindFromRequest();
        if (loginForm.hasErrors()) {
            return badRequest(login.render(loginForm));
        } else {
            session().clear();
            session("email", loginForm.get().email);
            return redirect(
                routes.Application.index()
            );
        }
    }
    

    我强烈建议您查看 完整 zentask 教程,如果您更想了解登录,您可以查看 zentask page 4(工作 zentask 示例也位于您的 play/samples/java/zentasks 内,所以它真的很容易上手)。

    相关部分包括使用authenticate 方法和Secured 类设置自己的登录表单、视图、控制器来处理实际的方法和或控制器动作保护。不要忘记将您的用户保存到数据库 - 否则没有太多要检查的内容;)

    编辑:

    如果您打开您的 zentask 教程,您可以看到一个示例,当控制器需要身份验证以及从会话中获取用户名时,请参阅注释:@Security.Authenticated(Secured.class)request().username() 的方法,它将用户名作为字符串返回.. 参见:Http.Request.html#username() documentation

    @Security.Authenticated(Secured.class)
    public class Projects extends Controller {
        /**
         * Display the dashboard.
         */
        public static Result index() {
            return ok(
                dashboard.render(
                    Project.findInvolving(request().username()),
                    Task.findTodoInvolving(request().username()),
                    User.find.byId(request().username())
                )
            );
        }
    
    ...
    

    方法本身例如User.find.byId(request().username()) 从数据库中加载完整的User 以供渲染模型。 :)

    但是请阅读我提供的教程,它的完整示例。干杯。

    【讨论】:

    • 当我调用身份验证方法时,我会调用数据库并检查用户凭据。我要避免的是根据用户的电子邮件再次拨打电话以获取用户。这就是为什么我想将用户对象作为一个整体存储
    • 稍后在java端调用本身并不昂贵,只需要几毫秒。如果您知道您将一直需要更多的用户名和 ID,您可以将它们都添加到会话中。但是,当您希望将 User 对象作为一个整体时,请从数据库中加载它。
    猜你喜欢
    • 2015-09-10
    • 2018-06-22
    • 2016-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    相关资源
    最近更新 更多