JSF:身份验证和授权,最佳前进方式

s1m*_*m0t 9 authentication jsf authorization java-ee jsf-2

我花了一整天谷歌搜索并在这里查看各种问题,试图找到实现身份验证和授权的最佳解决方案.我现在想出部分解决方案,但我希望有人可以填补空白.我知道下面有很多文字,但请耐心等待:O)

背景

我继承了一部分已完成的CRM应用程序,该应用程序目前使用JSF 2.0,JavaEE 6,JPA和PostgreSQL数据库.不幸的是,最初以他们无限的智慧开始构建这个Web应用程序的人决定最好将身份验证/授权留到最后 - 我现在必须把它放进去.

该应用程序基本上分为三层 - 视图,托管bean和DAO.这意味着托管bean特别"胖",因为它们包含所有业务逻辑,验证和导航逻辑.

身份验证/授权要求

  1. 基于表单的身份验证,验证存储在PostgreSQL数据库中的凭据.
  2. 唯一可以公开访问的页面(由匿名用户)将是登录页面.
  3. 我需要根据用户角色阻止访问应用程序的某些区域.例如,只有具有"管理员"角色的用户才能访问创建/编辑用户页面.
  4. 我还需要能够限制对页面某些区域的访问.例如,具有"销售代表"角色的用户应该能够查看客户详细信息,但只有在用户具有"客户服务"角色时才应显示保存/编辑按钮.

我在哪里

我计划做的第一件事是使用JAAS和Servlet 3.0 Login示例来遵循此用户身份验证和授权.我认为这将满足我的前3个要求.

为了在页面上显示/隐藏保存按钮等,我可以使用本SO答案中描述的技术.这将部分解决需求4,但我认为我仍然需要保护动作方法和托管bean本身.例如,我希望能够在customer bean上的save()方法中添加注释或内容,以确保只有具有"客户服务"角色的用户才能调用它 - 这就是我开始遇到问题的地方.

我想一个选择是做一些类似于我在视图中建议做的事情,并使用facesContext来检查当前用户是否"在角色中".我并不热衷于此,因为它会使我的代码混乱而宁愿使用注释.但是,如果我确实沿着这条路走下去,我将如何返回http 403状态?

javax.annotation.security.*注释似乎非常适合对应用程序区域进行明确定义访问,但据我所知,它们只能添加到EJB中.这意味着我需要将我的所有业务逻辑从目前驻留在新EJB的托管bean中移出.我认为这可以带来额外的好处,即将业务逻辑分成它自己的一组类(代理,服务或你选择调用它们的任何东西).这将是一个相当大的重构,但是由于缺乏单元测试或集成测试而无法帮助.我不确定访问控制的责任是否应该在这个新的服务级别 - 我认为它应该在托管bean上.

其他选择

在我的研究中,我发现很多人都提到了Spring和Seam等框架.我对Seam的经验有限,我觉得这个项目非常适合这个项目,而且我记得我相信它解决了我所拥有的授权问题,但我认为现在推出这个问题为时已晚.

我也见过Shiro在不同的地方提到过.看了10分钟的教程后,这似乎很合适,特别是与Deluan Quintao的taglib一起使用,但我一直无法找到任何教程或如何将它与JSF Web应用程序集成的示例.

我经常遇到的另一种选择是实施定制解决方案 - 这对我来说似乎很疯狂!

摘要

总而言之,我真的想要一些关于我是否在实现身份验证和授权方面走上正确道路的指导,以及我如何填写保护个别方法和/或托管bean(或至少是他们委托的代码)和/或我如何手动返回HTTP状态403.

s1m*_*m0t 3

经过大量研究后,我得出的结论是,首先,将我的应用程序的需求部署到完全实现 Java EE 规范的应用程序服务器(而不是像 Tomcat 这样的 servlet 容器)会受益。由于我正在处理的项目使用 Maven,这里的关键是正确设置依赖项 - 这并不容易,需要进行大量的谷歌搜索和反复试验:我确信有一种更科学的方法可以采取。

然后我必须创建一个 mysql 模块来让我的应用程序正确地与数据库对话,然后删除已实现的用于创建 DAO 的工厂并将它们转换为 EJB。我还必须更新 hibernate.cfg.xml 以引用我添加的数据源,并更新 persistence.xml 以将事务类型设置为 JTA 并引用 JTA 数据源。唯一的其他复杂之处是正在使用“在视图中打开会话”模式,这意味着当在视图中访问实体时,我最终会遇到休眠延迟初始化错误。我重新实现了过滤器,如本答案底部所示,以解决这个问题。我认为这是一种临时措施,可以让事情再次正常运行,然后我才有希望重构该区域并消除对过滤器的需求。

迁移到 JBoss 只花了一天多的时间,我确信如果我对 Java EE 和 Maven 更有经验的话,这件事可以做得更快。现在我处于一个很好的位置,能够将接缝 3 安全性放入项目中并利用它,而不是试图拼凑出一个解决方案,这本质上是我要采取的方向。Seam 3 的好处在于,您可以在一定程度上选择要使用的模块,而不必添加整个框架(如 Seam 2)。我认为许多其他模块也会有所帮助,并且将帮助我摆脱视图模式中的开放会话。

使用 Seam 确实让我担心的一件事是有人告诉我DeltaSpike。看起来它可能会取代seam,并且没有计划推出任何更多版本的seam。我决定,由于接缝仍然受到支持,并且如果 DeltaSpike 需要与接缝 3 一样长的时间才能实现,那么使用接缝 3 是相当安全的。

我希望能够抽出时间写一篇适当的博客文章,详细描述此迁移。

public class OSVRequestFilter implements Filter {

    private static final String UserTransaction = "java:comp/UserTransaction";

    private static Logger logger = LoggerFactory.getLogger(EntityManagerRequestFilter.class);

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            doFilter(request, response, chain, getUserTransaction());
        }
    }

    private UserTransaction getUserTransaction() throws ServletException {
        try {
            Context ctx = new InitialContext();
            return (UserTransaction)PortableRemoteObject.narrow(ctx.lookup(UserTransaction), UserTransaction.class);
        }
        catch (NamingException ex) {
            logger.error("Failed to get " + UserTransaction, ex);
            throw new ServletException(ex);
        }
    }

    private void doFilter(ServletRequest request, ServletResponse response, FilterChain chain, UserTransaction utx) throws IOException, ServletException {
        try {
            utx.begin();

            chain.doFilter(request, response);

            if (utx.getStatus() == Status.STATUS_ACTIVE)
                utx.commit();
            else 
                utx.rollback();
        }
        catch (ServletException ex) {
            onError(utx);
            throw ex;
        }
        catch (IOException ex) {
            onError(utx);
            throw ex;
        }
        catch (RuntimeException ex) {
            onError(utx);
            throw ex;
        }
        catch (Throwable ex){
            onError(utx);
            throw new ServletException(ex);
        }
    }

    private void onError(UserTransaction utx) throws IOException, ServletException {
        try {
            if ((utx != null) && (utx.getStatus() == Status.STATUS_ACTIVE))
                utx.rollback();
        }
        catch (Throwable e1) {
            logger.error("Cannot rollback transaction", e1);
        }
    }

    public void destroy() {
    }
}
Run Code Online (Sandbox Code Playgroud)