JSF:认证和授权,最佳前进方式

9
我已经花了一整天的时间在谷歌上搜索,并查看了这里的各种问题,试图找到最佳解决方案来实现身份验证和授权。我现在已经想出了部分解决方案,但希望有人能填补空白。我意识到下面有很多文字,但请耐心等待:O)
背景
我继承了一个部分完成的CRM应用程序,它目前使用JSF 2.0、JavaEE 6、JPA和PostgreSQL数据库。不幸的是,最初开始构建这个Web应用程序的人们在他们无限的智慧中决定,最好把身份验证/授权留到最后 - 现在我必须把它放进去。
该应用程序本质上分为三个层次 - 视图、托管的bean和DAO。这意味着托管的bean特别“肥”,因为它们包含所有的业务逻辑、验证和导航逻辑。
身份验证/授权要求
  1. 基于表单的身份验证,针对存储在PostgreSQL数据库中的凭据进行验证。
  2. 唯一可以被匿名用户公开访问的页面将是登录页面。
  3. 我需要根据用户角色防止对应用程序的某些区域进行访问。例如,只有具有“管理员”角色的用户才能访问创建/编辑用户页面。
  4. 我还需要能够限制页面的某些区域的访问。例如,具有“销售代表”角色的用户应该能够查看客户详细信息,但仅当用户具有“客户服务”角色时,保存/编辑按钮才应显示。

我的进展

我计划首先按照这个使用JAAS和Servlet 3.0登录进行用户身份验证和授权示例进行操作。我相信这将满足我的前三个要求。

为了在页面上显示/隐藏保存按钮等,我可以使用 this SO answer 中描述的技术。这将部分解决第4个要求,但我认为仍然需要保护操作方法和/或托管的bean本身。例如,我想能够向客户bean的save()方法添加注释或其他内容,以确保只有具有“客户服务”角色的用户才能调用它-这就是我开始遇到问题的地方。
我想一个选择是在视图中做类似于我建议在view中做的事情,并使用facesContext检查当前用户是否“在角色中”。我不喜欢这样做,因为它会使我的代码混乱,而宁愿使用注释。如果我确实走这条路,那么如何返回http 403状态?
javax.annotation.security.*注释似乎很适合声明性地定义应用程序区域的访问,但据我所知,它们只能添加到EJB中。这意味着我需要将所有业务逻辑从当前托管Bean中移出到新的EJB中。我认为这将有额外的好处,即将业务逻辑分离成自己的一组类(委托、服务或您选择的任何名称)。然而,这将是一个相当大的重构,缺乏单元测试或集成测试也不会帮助解决问题。我不确定访问控制的责任是否应该在这个新的服务级别上 - 我认为它应该在托管Bean上。
其他替代方案
在我的研究中,我发现很多人提到了Spring和Seam等框架。我对Seam有一些有限的经验,我认为它对这个项目来说是一个很好的选择,并且从我记得的情况来看,它解决了我正在遇到的授权问题,但我认为现在引入它已经太晚了。
我也看到了在各个地方提到的Shiro。看过10分钟教程后,它似乎很适合,特别是与Deluan Quintao的标签库结合使用,但我找不到任何如何将其与JSF Web应用程序集成的教程或示例。
另一个经常出现的选择是实现自定义解决方案-这对我来说似乎很疯狂!
总之,我真的想得到一些指导,了解我在实施身份验证和授权方面是否走在正确的道路上,以及如何填补保护单个方法和/或托管bean(或至少是它们委托给的代码)的缺失部分,或者如何手动返回HTTP状态403。

1
我一直找不到任何关于如何将其与JSF Web应用程序集成的教程或示例。现在有一个,但仍然不是理想的:http://balusc.blogspot.com/2013/01/apache-shiro-is-it-ready-for-java-ee-6.html - Arjan Tijms
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/

相比使用请求过滤器或JAAS,Spring Security是一个综合性的安全框架,可以解决大部分安全问题。您可以使用它来验证使用数据库领域认证的用户,授权并根据提供的身份验证信息进行必要的重定向。

您可以保护您编写的方法 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


谢谢你的回答。我有点采纳了你的建议,但稍微有些改变 - 我将使用Seam安全性代替 :O) - s1mm0t

3
经过大量的研究,我得出结论,首先,我的应用程序要求从部署在像Tomcat这样的servlet容器中受Java EE规范全面实现的应用服务器中获益。由于我正在使用Maven的项目,关键是正确设置依赖项 - 这并不容易,需要大量搜索和试错:我相信可以采取更科学的方法。
然后,我必须创建mysql模块以使我的应用程序能够正确地与数据库通信,然后删除已实现的工厂以创建DAO,并将其转换为EJB。我还必须更新hibernate.cfg.xml以引用我添加的数据源,并更新persistence.xml以将事务类型设置为JTA,并引用JTA数据源。唯一的其他复杂性是使用了Open Session In View模式,这意味着当在视图中访问实体时,我会遇到hibernate惰性初始化错误。我重新实现了筛选器,如此答案底部所示,以解决这个问题。我认为这是一个临时措施,在我希望重构这个区域并消除对筛选器的需求之前,让事情再次正常运行。
转移到JBoss只花了一天时间,如果我对Java EE和Maven更有经验的话,我相信可以更快完成。现在我已经到达这个阶段,我有能力将Seam 3安全性放入项目中,并利用它,而不是试图拼凑一个解决方案,这实质上是我要采取的方向。Seam 3的好处是您可以在某种程度上挑选您使用的模块,而不必添加整个框架(如Seam 2)。我认为其他一些模块也会很有帮助,它们将帮助我摆脱视图中的开放会话模式等问题。

使用Seam时让我担心的一件事是我听说过DeltaSpike。这似乎可能会取代seam,而且没有任何关于seam的版本计划。我已经决定,既然seam仍然受到支持,而且如果DeltaSpike像seam 3那样需要很长时间才能实现,那么使用seam 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() {
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接