[如果只想查看代码,请向下滚动]
动机
在RHQ中,我们需要一个安全域,该域可用于通过容器管理的安全性来保护REST-api及其Web应用程序。 过去,我只是使用经典的DatabaseServerLoginModule
对DatabaseServerLoginModule
进行身份验证。 现在RHQ还允许将用户包含在LDAP目录中,而上述模块未涵盖这些目录。 我有两个选择开始:
- 将LDAP登录模块复制到REST的安全域中
- 将安全域用于已经用于UI和CLI的REST-api
后一种选择当然有利于防止代码重复,所以我走了那条路。 并失败了。
我失败了,因为RHQ在启动时删除并重新创建了安全域,并且服务器检测到该错误并抱怨从rhq-rest.war引用的安全域突然消失了。
因此,下一个尝试:不要在启动时重新创建域,而仅添加/删除ldap-login模块(我说的是模块,因为实际上我们需要两个)。
这也没有按预期工作:
- 基础AS有时会进入需要重新加载的模式,并且未应用更改
- 除去ldap模块后,它们中的主体仍被缓存
- 刷新缓存不起作用,服务器进入需要重新加载的模式
因此,我现在要做的是为rest-security-domain实现一个登录模块,该模块仅委派给另一个进行身份验证,然后在成功时添加角色。
这样,rhq-rest.war便具有对该rest-security-domain的固定引用,而另一个安全域则可以像以前一样处理。
实作
让我们从standalone.xml文件中的代码段开始,该代码段描述安全域并参数化模块
<security-domain name="RHQRESTSecurityDomain" cache-type="default"><authentication><login-module code="org.rhq.enterprise.server.core.jaas.DelegatingLoginModule" flag="sufficient"><module-option name="delegateTo" value="RHQUserSecurityDomain"/><module-option name="roles" value="rest-user"/></login-module></authentication></security-domain>
因此,此定义设置了一个安全域RHQRESTSecurityDomain ,该域使用我将在稍后描述的DelegatingLoginModule。 传递了两个参数:
- proxyTo:要验证用户身份的另一个域的名称
- 角色:以逗号分隔的要添加到主体的模块列表(以及web.xml的security-constraint部分中需要的模块)
对于代码,我没有显示完整清单。 你可以在git中找到它 。
为了使我们的生活更轻松,我们并没有自己实现所有功能,而是扩展了现有的UsernamePasswordLoginModule
并仅覆盖某些方法。
public class DelegatingLoginModule extends UsernamePasswordLoginModule {
首先,我们使用传递的选项初始化模块,并使用我们委派给的域创建一个新的LoginContext:
@Overridepublic void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,Map<String, ?> options) {super.initialize(subject, callbackHandler, sharedState, options);/* This is the login context (=security domain) we want to delegate to */String delegateTo = (String) options.get("delegateTo");/* Now create the context for later use */try {loginContext = new LoginContext(delegateTo, new DelegateCallbackHandler());} catch (LoginException e) {log.warn("Initialize failed : " + e.getMessage());}
有趣的部分是login()
方法,在该方法中我们获取用户名/密码并将其存储以供以后使用,然后我们尝试登录到委托域,如果成功,则告诉super我们成功了,以便它可以发挥作用。 。
@Overridepublic boolean login() throws LoginException {try {// Get the username / password the user entred and save if for later useusernamePassword = super.getUsernameAndPassword();// Try to log in via the delegateloginContext.login();// login was success, so we can continueidentity = createIdentity(usernamePassword[0]);useFirstPass=true;// This next flag is important. Without it the principal will not be// propagatedloginOk = true;
这里需要loginOk
标志,以便超类将调用LoginModule.commit()
并选择主体和角色。
不将其设置为true将导致成功login()
但没有附加主体。
if (debugEnabled) {log.debug("Login ok for " + usernamePassword[0]);}return true;} catch (Exception e) {if (debugEnabled) {LOG.debug("Login failed for : " + usernamePassword[0] + ": " + e.getMessage());}loginOk = false;return false;}}
成功后,super将调用以下两种方法来获取主体及其角色:
@Overrideprotected Principal getIdentity() {return identity;}@Overrideprotected Group[] getRoleSets() throws LoginException {SimpleGroup roles = new SimpleGroup("Roles");for (String role : rolesList ) {roles.addMember( new SimplePrincipal(role));}Group[] roleSets = { roles };return roleSets;}
现在,最后一部分是回调处理程序,我们委托的其他域将使用该回调处理程序从我们那里获取凭据。 它是经典的JAAS登录回调处理程序。 首先让我感到困惑的一件事是,该处理程序在登录期间被调用了几次,我认为这是错误的。 但是实际上,它被调用的次数与RHQUserSecurityDomain中配置的登录模块的数量相对应。
private class DelegateCallbackHandler implements CallbackHandler {@Overridepublic void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {for (Callback cb : callbacks) {if (cb instanceof NameCallback) {NameCallback nc = (NameCallback) cb;nc.setName(usernamePassword[0]);}else if (cb instanceof PasswordCallback) {PasswordCallback pc = (PasswordCallback) cb;pc.setPassword(usernamePassword[1].toCharArray());}else {throw new UnsupportedCallbackException(cb,"Callback " + cb + " not supported");}}}}
同样,完整的代码在RHQ git仓库中可用。
调试(在EAP 6.1 alpha或更高版本中)
如果您编写了这样的登录模块,但该模块不起作用,则需要对其进行调试。 从通常的方法开始,以了解我的login()
方法是否按预期工作,但登录失败。 我添加了打印语句等,以发现从未调用过getRoleSets()
方法。 但是,一切看起来还不错。 我进行了一些谷歌搜索,发现了这个不错的Wiki页面 。 可以告诉Web应用进行审核日志记录
<jboss-web><context-root>rest</context-root><security-domain>RHQRESTSecurityDomain</security-domain><disable-audit>false</disable-audit>
仅此标志是不够的,因为您还需要设置适当的记录器,这在Wiki页上进行了说明。 启用此功能后,我看到了类似
16:33:33,918 TRACE [org.jboss.security.audit] (http-/0.0.0.0:7080-1) [Failure]Source=org.jboss.as.web.security.JBossWebRealm;
principal=null;request=[/rest:….
因此很明显,登录模块未设置主体。 然后查看超类中的代码,将我带到上面提到的loginOk
标志。
现在,一切都正确设置了,自动日志看起来像
22:48:16,889 TRACE [org.jboss.security.audit] (http-/0.0.0.0:7080-1) [Success]Source=org.jboss.as.web.security.JBossWebRealm;Step=hasRole; principal=GenericPrincipal[rhqadmin(rest-user,)]; request=[/rest:cookies=null:headers=authorization=user-agent=curl/7.29.0,host=localhost:7080,accept=*/*,][parameters=][attributes=];
因此,在这里您看到主体rhqadmin已登录并获得了剩余用户分配的角色,这是web.xml中security-constraint元素中的一个匹配对象。
进一步查看
我已将以上内容作为环聊直播进行了介绍 。 不幸的是,当我在解释时打字时,G +会不时使我静音。
视频播放完后,我还有其他问题,最终让我重新思考启动阶段,以防用户安装了启用了LDAP的RHQ先前版本。 在这种情况下,安装程序仍将安装初始的仅基于DB的RHQUserSecurityDomain,然后在启动bean中,检查a)系统设置中是否启用了LDAP,以及b)登录模块是否实际存在。 如果a)匹配并且不存在,我们将安装它们。
此Bugzilla条目还包含有关整个故事的更多信息。
翻译自: https://www.javacodegeeks.com/2013/05/creating-a-delegating-login-module-for-jboss-eap-6-1.html