我已经实现了一个适用于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的最佳做法:
Principal
实施包含" accountIdentifier"在LoginModule.commit()
期间设置的属性。AccountIdentifier
作为单独的Principal
实施,并在Subject
期间添加到LoginModule.commit()
。第一种选择是最简单的,但这似乎也违背了从账户中隔离个人识别信息的目的(这是我为满足即将到来的欧洲GDPR要求而需要做的事情)。
我是否应该添加"用户"主题(包含电子邮件地址的主体)到主题?
答案 0 :(得分:2)
JAAS和Servlet规范之间存在多种关于身份验证和用户原则的不兼容性。因此,Spring使用与Tomcat不同的JAAS集成方法。
这个答案记录了一种以适合Tomcat和Spring的方式实现JAAS登录模块的综合方法。
为清楚起见,这里从问题中复制了实现用户原则的两个选项:
Principal
实施包含" accountIdentifier"在LoginModule.commit()
期间设置的属性。AccountIdentifier
作为单独的Principal
实施,并在Subject
期间添加到LoginModule.commit()
。选项1的不良副作用是将不同形式的个人身份信息加在一起,这在某些环境中可能会违反欧洲的GDPR规则(会话可能会被序列化到磁盘上,而且这些信息也随之而来)。
选项2分离出个人身份信息,但必须以克服Servlet规范和Tomcat JAAS实施中的若干限制的方式实施。
下面详细介绍了这些限制,粗体部分概述了要点。
JAASRealm
要求Subject
的后备集合保留Principal
的排序。使用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始终被视为用户 主体。
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
时,请避免使用多个用户类名称。
违反上述两条规则可能会导致未定义和/或不一致的行为。
AuthorityGranter
,对非框架Tomcat servlet使用ServletFilter
为了选项2,我避免使用JAASRealm
,因为根据上述所有文档,它并不忠实地遵守JAAS。这让我回到纯粹的ServletFilter
方法。
DefaultJaasAuthenticationProvider包含授权所需的一切:多个用户主体,角色和ACL组。不幸的是,这个类只是部分可序列化的,这意味着我不能将该类包装为Principal
并返回它。
为了满足Spring的AuthorityGranter,实现getUserPrincipal将Principal
映射到角色名称 - 这可以完全控制映射的执行方式。
由于AuthorityGranter
在Spring Framework之外不可用,我还实现了一个ServletFilter
,它使用类似的方法为我的非Spring webapp映射角色。暂时,我使用HttpServletRequestWrapper
从会话属性(在身份验证期间存储在会话中)中读取Principal和Roles并覆盖isUserInRole和{{3}}。最后,我将重新审视JAASRealm,看看它是否包含处理这件作品的任何功能,但我还没有完成。