9

我花了一整天的时间在谷歌上搜索并查看这里的各种问题,试图提出实现身份验证和授权的最佳解决方案。我现在已经提出了部分解决方案,但我希望有人可以填补空白。我知道下面有很多文字,但请多多包涵:O)

背景

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

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

身份验证/授权要求

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

我在哪里

我计划做的第一件事是遵循这个使用 JAAS 和 Servlet 3.0 登录示例的用户身份验证和授权。我相信这将满足我的前 3 个要求。

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

我想一种选择是做类似于我提议在视图中做的事情,并使用 facesContext 检查当前用户是否“在角色中”。我对此并不热衷,因为它只会弄乱我的代码,而宁愿使用注释。但是,如果我确实沿着这条路线走,我将如何返回 http 403 状态?

javax.annotation.security.* 注释似乎非常适合以声明方式定义对应用程序区域的访问,但据我了解,它们只能添加到 EJB 中。这意味着我需要将我的所有业务逻辑从它当前所在的托管 bean 中移到新的 EJB 中。我认为这将具有将业务逻辑分离到它自己的一组类(委托、服务或您选择调用它们的任何内容)中的额外好处。这将是一个相当大的重构,但是不会因为缺少单元测试或集成测试而有所帮助。我不确定访问控制的责任是否应该在这个新的服务级别上——我认为它应该在托管 bean 上。

其他选择

在我的研究中,我发现很多人提到了 Spring 和 Seam 等框架。我对 Seam 的经验有限,我认为它非常适合这个项目,据我回忆,我相信它解决了我遇到的授权问题,但我认为现在介绍它为时已晚.

我也看到很多地方都提到了Shiro。看了10 分钟的教程,这似乎很合适,尤其是与Deluan Quintao 的 taglib结合使用,但我一直找不到任何教程或示例来说明如何将其与 JSF Web 应用程序集成。

我经常出人意料地遇到的另一种选择是实施自定义解决方案 - 这对我来说似乎很疯狂!

概括

总而言之,我真的很想得到一些指导,说明我在实现身份验证和授权方面是否走在正确的道路上,以及我如何填补保护单个方法和/或托管 bean 的缺失部分(或者至少是他们委托给的代码)和/或我如何手动返回 HTTP 状态 403。

4

2 回答 2

3

您是否尝试过使用 Spring Security 进行任何操作 - 最新版本为 3

http://janistoolbox.typepad.com/blog/2010/03/j2ee-security-java-serverfaces-jsf-spring-security.html

http://ocpsoft.org/java/jsf-java/spring-security-what-happens-after-you-log-in/

spring security 不是使用请求过滤器或使用 JAAS,而是一个全面的安全框架,它将解决您的大部分安全问题。您可以使用它使用 db 领域对用户进行身份验证,根据提供的身份验证信息授权他并根据需要重定向。

您可以保护您编写的方法 http://blog.solidcraft.eu/2011/03/spring-security-by-example-securing.html

@PreAuthorize("hasRole('ROLE_XXX')") 就是这样

使页面的某些元素安全.. //内容

更多阅读和示例 http://static.springsource.org/spring-security/site/petclinic-tutorial.html

于 2012-05-11T16:06:42.133 回答
3

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

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

迁移到 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() {
    }
}
于 2012-05-16T16:48:15.747 回答