JAAS登录模块中的用户主体 - 作为单独的Principal或Bean属性的额外属性?

时间:2017-10-25 14:18:04

标签: java spring authentication tomcat jaas

我已经实现了一个适用于Spring 4和Struts 2.3的JAAS LoginModule。同样的LoginModule也可以通过Tomcat 8.0 / 8.5中的ServletFilter来调用,以验证和授权对Spring框架之外的Servlet的请求。

LoginModule使用java.security.acl.Group的简单实现,并将用户和角色与java.security.Principal的两个简单实现分开。通过"简单"我的意思是满足接口的最小实现。

"用户"实现将name属性映射到唯一的用户名(实际上是电子邮件地址)。由于电子邮件地址是唯一的但可能会更改,因此帐户数据库包含唯一帐户标识符(GUID),用于分配组,角色和记录服务请求(同时还对我们的用户进行匿名)。在我的模型中,AccountIdentifier有自己的类。基本上,我有两个唯一的帐户标识符,但由于需要将电子邮件地址提供给LoginModule进行身份验证,因此它最终成为用户主体的基础。

帐户标识符当前未传播到Subject中的LoginModule,但现在我需要它才能记录服务请求。

我看到了通过Subject提供帐户标识符的两种方法,但我不确定哪种是JAAS的最佳做法:

  1. 扩展我现在的用户" Principal实施包含" accountIdentifier"在LoginModule.commit()期间设置的属性。
  2. AccountIdentifier作为单独的Principal实施,并在Subject期间添加到LoginModule.commit()
  3. 第一种选择是最简单的,但这似乎也违背了从账户中隔离个人识别信息的目的(这是我为满足即将到来的欧洲GDPR要求而需要做的事情)。

    我是否应该添加"用户"主题(包含电子邮件地址的主体)到主题?

1 个答案:

答案 0 :(得分:2)

JAAS和Servlet规范之间存在多种关于身份验证和用户原则的不兼容性。因此,Spring使用与Tomcat不同的JAAS集成方法。

这个答案记录了一种以适合Tomcat和Spring的方式实现JAAS登录模块的综合方法。

为清楚起见,这里从问题中复制了实现用户原则的两个选项:

  1. 扩展我现在的用户" Principal实施包含" accountIdentifier"在LoginModule.commit()期间设置的属性。
  2. AccountIdentifier作为单独的Principal实施,并在Subject期间添加到LoginModule.commit()
  3. 选项1的不良副作用是将不同形式的个人身份信息加在一起,这在某些环境中可能会违反欧洲的GDPR规则(会话可能会被序列化到磁盘上,而且这些信息也随之而来)。

    选项2分离出个人身份信息,但必须以克服Servlet规范和Tomcat JAAS实施中的若干限制的方式实施。

    下面详细介绍了这些限制,粗体部分概述了要点。

    JAASRealm要求Subject的后备集合保留Principal的排序。

    Tomcat 8.5 JAAS Realm文档声明:

      

    使用JAASRealm使开发人员能够实际组合   任何可以想象的安全领域与Tomcat的CMA。

    但接着陈述:

      

    虽然未在JAAS中指定,但您应该创建单独的类   区分用户和角色,扩展javax.security.Principal   以便Tomcat可以告诉您从登录模块返回的Principal   是用户,哪些是角色(请参阅org.apache.catalina.realm.JAASRealm)。   无论如何,返回的第一个Principal 始终被视为用户   主体。

    请注意,上述Tomcat文档使用短语" 用户Principal" 。虽然JAAS API建议将用户和角色实现为扩展javax.security.Principal的不同类,但这与Servlet规范不兼容,因为HttpServletRequest.getUserPrincipal()仅允许返回单个Principal:

      

    返回包含其名称的javax.security.Principal对象   当前经过身份验证的用如果用户尚未通过身份验证,   该方法返回null。

    严格阅读上述文档表明它应该包含" ...当前经过身份验证的用户的名称" ,但为了满足我原来的目标,我我将其解释为" ...经过身份验证的主题" 的任何名称或标识符。这更接近于com.sun.security.auth.UserPrincipal文档(即"由用户名或帐户名称标识的用户主体" )。

    由于Tomcat JAASRealm和Servlet规范HttpServletRequest中的上述限制,如果要将帐户标识符传播到请求,这一点显然很重要a ServletFilter(只能访问当前会话,请求和响应),它必须包含在第一个Principal中(因此原始问题中的选项1将满足此要求,或仅满足选项2)如果它首先出现,我不需要原始用户名)。我相信我真正需要的只是帐户标识符,所以我现在坚持使用第二个选项,在那里我提交了一个" EmailAddressPrincipal"到MyLoginModule我收到一个" AccountIdentifierPrincipal"通过Subject返回(即MyLogin.commit()添加" AccountIdentifierPrincipal"作为第一个校长。

    关于校长的确切顺序,JAASRealm文档实际上有些矛盾,这取决于您阅读的部分:

      

    因为这个Realm迭代了返回的Principals   Subject.getPrincipals(),它将标识第一个Principal   匹配"用户类"列为此用户的主体

    VS

      

    无论如何,返回的第一个Principal始终被视为用户   主体。

    Servlet API不保证Principle 返回的Subject的排序。

    基本上,如果我要创建一个模仿ServletFilter正在做的JAASRealm,那么身份验证将如下所示(特别注意迭代器):

    final LoginContext loginContext = new LoginContext(MyLoginModule.JAAS_REALM, new DefaultCallbackHandler(username, password));
    loginContext.login();
    final Subject subject = loginContext.getSubject();
    request.getSession().setAttribute("AUTH_USER_PRINCIPAL", subject.getPrincipals(AccountIdentifierPrincipal.class).iterator().next());
    request.getSession().setAttribute("AUTH_ROLE_PRINCIPALS", subject.getPrincipals(MyRolePrincipal.class));
    

    不幸的是,这与javax.security.auth.Subject的构造函数直接冲突,java.util.Set强制要求Set.iterator()用作Principal的后备集合。此外,SynchronizedSet文档声明:

      

    元素以无特定顺序返回(除非此组是   一个提供保证的类的实例。

    我们对Subject的最早访问权限在LoginModule.initialize()方法中,不幸的是,在LoginContext的内部(我认为)中调用了这种方法。这意味着我们无法控制用作Set的后备集合的Principals的确切子类,因此无法控制它们的排序。当到达ServletFilter时,它是javax.security.auth.Subject,因此它甚至不清楚原始类是什么,或者是否重新排序。

    这表示为了使JAASRealm按预期工作,只能提供单个用户主体。该中间层中没有任何界面可以清楚地确定Subject Principals的顺序。

    结论

    使用JAASRealm时,Principal期间只应将Subject声明的用户类型添加到commit

    使用JAASRealm时,请避免使用多个用户类名称。

    违反上述两条规则可能会导致未定义和/或不一致的行为。

    解决方案:对Spring使用AuthorityGranter,对非框架Tomcat servlet使用ServletFilter

    为了选项2,我避免使用JAASRealm,因为根据上述所有文档,它并不忠实地遵守JAAS。这让我回到纯粹的ServletFilter方法。

    DefaultJaasAuthenticationProvider包含授权所需的一切:多个用户主体,角色和ACL组。不幸的是,这个类只是部分可序列化的,这意味着我不能将该类包装为Principal并返回它。

    为了满足Spring的AuthorityGranter,实现getUserPrincipalPrincipal映射到角色名称 - 这可以完全控制映射的执行方式。

    由于AuthorityGranter在Spring Framework之外不可用,我还实现了一个ServletFilter,它使用类似的方法为我的非Spring webapp映射角色。暂时,我使用HttpServletRequestWrapper从会话属性(在身份验证期间存储在会话中)中读取Principal和Roles并覆盖isUserInRole和{{3}}。最后,我将重新审视JAASRealm,看看它是否包含处理这件作品的任何功能,但我还没有完成。