WebSphere Operational Decision Manager 在中国已经发布了 8.6 版本,越来越多的企业级用户选择了 ODM 产品作为规则管理平台,并得到了相当多的正面回馈。然而在产品实施过程中,大家对于 ODM 的用户权限管理,纷纷提出了类似的问题:除了在 WebSphere 或 WebLogic 等服务器控制台中手工配置外,还有没有其它更好的选择?是否可以和企业已有的用户权限系统集成使用?
本文就将回答这些问题。并且通过示例代码,展示在 WebSphere 和 WebLogic 上配置客户定制的独立注册表进行权限验证的过程。
回页首
ODM 产品中有两个部件是基于网络应用的:决策中心 Decision Center,和决策执行平台 RES。其中决策中心有着比较多的用户组和角色,自然而然成为用户们特别关注的部件,我们就以它为例,看看是如何验证用户权限和角色授权的。
决策中心是一个标准的 J2EE 应用,其中涉及到的所有验证 (Authentication) 和授权 (Authorization) 都是通过网络应用服务器的规范来完成的。ODM 并没有规定权限验证的方法和手段,只要是应用服务器暴露出的接口,ODM 就能使用。例如在 WebSphere 中定义了 LDAP 的接口,那么 ODM 就可以通过 WebSphere 验证 LDAP 中的信息。当用户在决策中心输入登录信息后,WebSphere 会在 LDAP 中查找相应的用户名,并验证密码和用户组。取得用户组 (Group) 后,决策中心根据用户自定义的映射关系找到相应角色并授权。
角色 (Role) 是定义在 decision center 应用包里的 web.xml 文件中。用户可以在这个文件中新增或删减角色,之后必须重新打包部署应用包。角色与用户组之间的映射关系,是在部署阶段定义的。用户可以在服务器上手工配置,或者编写自动脚本实现。简单地说,在部署决策中心前,服务器已经接入权限验证源 (LDAP,文件系统,数据库等);应用包内,角色已经定义在 web.xml 中;部署时,关联用户组和角色。使用时,系统根据不同的角色,分配不同的视图和操作权限。
下图显示了决策中心默认定义好的几种角色,也是最常用的。右栏中的 Mapped groups 就是用户自己在权限验证源中的 group。
图 1 决策中心角色定义
回页首
身份安全验证是一种机制,调用者用其来证明自己正在代表特定用户或系统进行操作。身份验证用凭据(例如用户名/密码组合)来判断身份。WebSphere Application Server 提供了非常强大的 J2EE 安全认证机制,提供了 4 种用户注册表 (安全认证的数据源),分别是:
用法 1 和用法 2,顾名思义就是使用已有的操作系统用户权限,或者 LDAP 目录,接入 WAS 服务器充当后台验证的数据源。这两种用法相对比较简单,用户在 WAS 控制台上,输入主机名、端口号等信息,即可成功。具体步骤请参考 WebSphere 官方文档。用法 4 联合存储库,是指 WAS 管理下的文件系统或 LDAP。例如在 WAS 控制台上手动设置用户与组,这些信息就保存在受管的文件系统内。ODM 默认的样本控制台 sample server 使用的就是这种方法。
用法 3 是本文要着重叙述的。用户注册表支持任何形式的存储库,通常情况下为文件系统或关系数据库。WAS 定义了一个 UserRegistry 接口。当用户已经有独立的第三方安全验证存储库,而且不适合迁移到本地操作系统或 LDAP 上去时,就可以使用这个接口,之后 WAS 就可以使用既有数据进行所有与安全相关的操作。
UserRegistry 接口定义了一系列安全验证必须的方法,通过密码或者证书验证用户权限;读取用户和用户组的信息,分配各自不同的资源准入资格。因此在实现该接口时,用户需要考虑如何把第三方存储库中的信息,映射到定制代码中。
特别需要注意的是,一切与第三方存储库的操作, 都应该在代码中实现,而不是依赖服务器本身的功能。WebSphere 服务器启动时,安全机制是最先激活的。如果依赖了其它功能,整个验证会失败。例如应该在代码中实现数据库连接功能,而不是通过 WAS 的 data sources 来配置数据库。
UserRegistry 接口处理以下用户信息:
回页首
下面我们就以决策中心 Decision Center 为例,演示如何在 WebSphere 服务器上整合数据库类型的用户定制表。
本章节示例运行环境:
我们使用 Derby 数据库作为第三方安全认证数据源。用户可以使用或参考附件代码中的 derby.sql 创建数据库 mydb, 建表, 并插入测试数据。用户也可以选择使用其它数据库产品,建立类似的表结构。
附件中 mydb 文件夹是已经生成的 derby 实例,用户可以直接拷贝到 derby 产品安装目录下使用。本例中数据库运行在默认端口 1527。
如图 2 所示,mydb 包含了三张表。
图 2 表明了三张表之间的关联
在 Eclipse 中导入附件 websphere 文件夹下的 java 项目 DBRegistrySample;
如用户自行编写代码,特别注意 package 名必须是 com.ibm.websphere.security;
在 lib 目录中添加其它必须的依赖 jar 包,例如数据库驱动程序;
图 3 示例 Java 项目的结构
在 Java Build Path 中添加 /WebSphere/AppServer/plugins/com.ibm.ws.runtim.jar。接口 UserRegistry 在这个 jar 包中定义。编译 java 项目,确保没有错误。
图 4 Build Path 设置
UserRegistry 中,除了 createCredential 外,其余所有的方法必须全部实现。下面我们着重查看几个方法。
初始化 UserRegistry 的方法,所有定义在定制注册表中的 properties 都要传到这个方法里来,例如数据库的链接信息,登录信息等。当 WAS 服务器开启时,调用这个方法来初始化注册表。当进入管理员控制台时,也会调用这个方法进行确认。在 DBRegistrySample 中,initialize 方法读取了 derby 数据库的连接信息。
清单 1 WAS initialize 代码
@Override public void initialize(Properties props) throws CustomRegistryException, RemoteException { try { if (props != null) { derbyUrl = props.getProperty("derbyUrl"); derbyUser = props.getProperty("derbyUser"); derbyPassword = props.getProperty("derbyPassword"); } } catch (Exception ex) { throw new CustomRegistryException(ex.getMessage(), ex); } if (derbyUrl == null || derbyUser == null || derbyPassword == null) { throw new CustomRegistryException("users/groups information missing"); } }
checkPassword 用来验证请求的用户名与密码是否合法,这个方法是定制注册表的关键所在,每个待验证用户都会生成一个证书。其返回值通常是用于验证的用户名字 user security name。这个用户名字也是 enterprise bean 调用 getCallerPrincipal 的返回值,或者 servlet 调用 getUserPrincipal 和 getRemoteUser 的返回值。
在很多场景都将用到 checkPassword 方法。例如在注册表已经初始化后,登录 WAS 管理控制台,就需要检查用户名密码。当用户需要访问产品中受保护的资源前,也将调用这个方法来验证用户的合法性。DBRegistrySample 通过 user name,在数据库中查找到对应的密码,并与用户输入密码比对,若正确则返回该用户名字。
清单 2 WAS checkPassword 代码
@Override public String checkPassword(String username, String passwd) throws PasswordCheckFailedException, CustomRegistryException, RemoteException { File myFile = new File("D:/"+passwd); if (!myFile.exists()) try { myFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } boolean validUser = false; try { Class.forName(DERBY_DRIVER).newInstance(); conn = DriverManager.getConnection(derbyUrl, derbyUser, derbyPassword); if (null != conn) { ps = conn.prepareStatement("select password from Users where userId="+"'"+username+"'"); rs = ps.executeQuery(); if (rs.next()) { if (rs.getString("password").equals(passwd)) { validUser = true; } } } } catch (Exception e) { System.out.println("DB Exception!" + e.toString()); } finally { if (null != rs) { try { rs.close(); } catch (SQLException e) { } } if (null != ps) { try { ps.close(); } catch (SQLException e) { } } if (null != conn) { try { conn.close(); } catch (SQLException e) { } } } if (validUser == false) { throw new PasswordCheckFailedException("Password check failed for user: " + username); } return username; }
mapCertificate 方法从浏览器中 X.509 安全证书链取得用户名。完整的安全证书链被传到该方法中,进行验证并从中提取用户信息。当验证过程中需要提供证书时,就要使用这个方法。例如在网络应用中,如果使用了 CLIENT-CERT 类型的安全验证,那么 WAS 服务器调用这个方法,将一个证书映射到注册表中的一个用户。
清单 3 WAS mapCertificate 代码
@Override public String mapCertificate(X509Certificate[] cert) throws CertificateMapNotSupportedException, CertificateMapFailedException, CustomRegistryException, RemoteException { String name = null; X509Certificate cert1 = cert[0]; try { // map the SubjectDN in the certificate to a userID. name = cert1.getSubjectDN().getName(); } catch (Exception ex) { throw new CertificateMapNotSupportedException(ex.getMessage(), ex); } if (!isValidUser(name)) { throw new CertificateMapFailedException("user:" + name + "is not valid"); } return name; }
getRealm 方法返回注册表所在的域 realm。域名界定了注册表验证用户的安全领域。如果该方法返回的是空值,那么就使用默认值 customRealm。
清单 4 WAS getRealm 代码
@Override public String getRealm() throws CustomRegistryException, RemoteException { String name = "customRealm"; return name; }
图 5 全局安全配置页面
在 General properties 页面,参照图 6 进行配置:
配置完毕后,点击 Apply,再点击 Save,此时会有提示,必须等到该注册表方式被激活后,这些配置才会起效。
图 6 注册表属性配置
图 7 激活定制表配置
部署决策中心。在第 9 步 map security roles to users and groups 中,WAS 服务器会从数据库中得到所有的组名,供用户选择。
图 8 角色中心角色映射
若服务器上已经部署了决策中心,可以通过 Websphere enterprise applications > teamserver_version > Security role to user/group mapping 进行修改。
登录决策中心 (http://localhost:9080/teamserver),验证注册表中的用户权限
使用管理员 bob 登录,能看到决策中心所有的标签页。
使用普通用户 rtsUser1 登录,无法看到 Configure 页面。
图 9 决策中心主页
回页首
在 WebLogic 服务器中,身份验证提供程序用于验证用户或系统进程的标识。身份验证提供程序还可以记忆、传输标识信息,并通过主题 Topic,按需向系统的各种组件提供标识信息。在身份验证过程中,通过对委托人的真实性进行签名和验证,委托人验证提供程序便可为主题中的委托人(用户或组)提供额外的安全保护。
用户和组都可由应用服务器用作委托人。委托人是作为身份验证的结果指定给用户或组的一个标识。Java 身份验证和授权服务 (JAAS) 要求将主题作为身份验证信息(包括委托人)的容器。每个委托人都存储在表示同一用户标识另一方面的同一主题中。作为成功进行身份验证的一部分,将对委托人进行签名并将其存储在主题中以备将来使用。
图 10 描述了用户、组、委托人和主题之间的关系
与 WebSphere 相类似,WebLogic 提供了以下可供直接使用的接口:
以上 5 项是 WebLogic 内部已实现,可通过控制台配置直接使用的身份验证提供程序。如果这些还无法满足用户需求,那么就需要开发自定义身份验证提供程序了。WebLogic 定义了 LoginModule 接口来处理各种身份验证机制。该接口基于 JAAS 规范,使用主题作为身份验证信息的容器。一个企业应用程序可以包含多个 LoginModule,但是 LoginModule 和身份验证提供程序则是 1 对 1 的关系。例如实现网页口令输入验证和指纹扫描验证,就必须实现两个 LoginModule 接口,并配置两套身份验证提供程序。
图 11 展示了在 WebLogic 中用户身份验证的全过程
为了帮助开发自定义身份验证提供程序,WebLogic 指定了一套标准步骤:
回页首
下面我们就以决策中心 Decision Center 为例,演示如何在 WebLogic 服务器上实现数据库类型的身份验证提供程序。
本章节示例运行环境:
我们使用与 WebSphere 示例中同样的 derby 数据库作为第三方安全验证数据源。用户可以使用附件中的 derby.sql 创建数据库 mydb 并生成测试数据。也可以直接使用附件中 mydb 文件夹,确保 derby 运行在默认端口 1527 下。
在 Eclipse 中导入附件 weblogic 文件夹下的 java 项目 dbuserprovider;
在 Java Build Path 中添加/Oracle_Home/wlserver/server/lib/wls-api.jar。接口 LoginModule 和 AuthenticationProviderV2 在这个 jar 包中定义。编译 java 项目,会出现若干' DBUserAuthenticatorMBean cannot be resolved to a type'的错误,我们先不用理会。除此以外,确保没有其它编译错误。
图 12 WL 项目依赖类
每个带有后缀“Provider”的 SSPI(如 CredentialProvider)均可向 WebLogic 安全框架公开安全提供程序的服务。借此便可以操纵(初始化、启动、停止等)安全提供程序。
该方法可获取与身份验证提供程序相关联的 LoginModule 的信息,以 AppConfigurationEntry 的形式返回。AppConfigurationEntry 是一种 Java 身份验证和授权服务 (JAAS) 类,包含 LoginModule 的类名、LoginModule 的控制标志,以及 LoginModule 的配置选项映射(用其可将其他配置信息传入 LoginModule)。
清单 5 WL getLoginModuleConfiguration 代码
public AppConfigurationEntry getLoginModuleConfiguration() { // Don't pass in any special options. // By default, the simple sample authenticator's login module // will authenticate (by checking that the passwords match). HashMap options = new HashMap(); return getConfiguration(options); }
该方法可获取与标识声明提供程序相关联的 LoginModule 的信息,如 LoginModule 的控制标志等。如果要让标识声明提供程序与身份验证提供程序使用同一 LoginModule,则 getAssertionModuleConfiguration 方法的实现将返回 null 值。
清单 6 WL getAssertionModuleConfiguration 代码
public AppConfigurationEntry getAssertionModuleConfiguration() { // Pass an option indicating that we're doing identity // assertion (vs. authentication) therefore the login module // should only check that the user exists (instead of checking // the password) HashMap options = new HashMap(); options.put("IdentityAssertion", "true"); return getConfiguration(options); }
该方法可获取对委托人验证提供程序运行时类(即 PrincipalValidator SSPI 实现)的引用。大多数情况下均可使用 WebLogic 委托人验证提供程序。
方法可获取对新标识声明提供程序运行时类(即 IdentityAsserterV2 SSPI 实现的引用。大多数情况下此方法的返回值都将是 null。
initialize 方法用于初始化 LoginModule。该方法将参数作为主题,可在此主题中存储生成的委托人、身份验证提供程序将用于回调身份验证信息容器的 CallbackHandler、所有共享状态信息的映射、以及配置选项的映射。CallbackHandler 是一种高度灵活的 JAAS 标准,允许将不定数量的参数作为复杂对象传递给方法。
清单 7 WL initialize 代码
public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { // only called (once!) after the constructor and before login logger.debug("DBUserLoginModuleImpl.initialize"); this.subject = subject; this.callbackHandler = callbackHandler; // Determine if we're in identity assertion or authentication mode isIdentityAssertion = "true".equalsIgnoreCase((String)options.get("IdentityAssertion")); // Get the object that manages the user and group definitions database = (DatabaseAuthenticatorHelper)options.get("database"); }
login 方法用于尝试通过回调身份验证信息的容器,来对用户进行身份验证并为用户创建委托人。如果已配置多个 LoginModule 作为多个身份验证提供程序的一部分,则会按配置每个 LoginModule 的顺序调用此方法。对于每个 LoginModule,有关登录是否成功(即是否已创建委托人)的信息都会得到存储。
如使用附件代码,那么在整个 LoginModule 模块中唯一需要用户修改的就是 login 方法中校验用户名密码的实现细节。在本例中,通过帮助类 DatabaseAuthenticatorHelper 连接数据库,取得相应用户信息进行校验。
清单 8 WL login 代码
public boolean login() throws LoginException { logger.debug("DBUserLoginModuleImpl.login"); Callback[] callbacks = getCallbacks(); loginSucceeded = false; // Get the user name. String userName = getUserName(callbacks); if (userName.length() > 0) { // We have a user name String s1 = null; try { s1 = database.getUserPassword(userName); s1 = s1.trim(); }catch(Exception exception) {} String s2 = getPasswordHave(userName, callbacks).trim(); if(null != s1 && s1.equals(s2)) { // since the login succeeded, add the user and its groups to the // list of principals we want to add to the subject. loginSucceeded = true; principalsForSubject.add(new WLSUserImpl(userName)); addGroupsForSubject(userName); logger.info("Result of login:" + loginSucceeded); return loginSucceeded; } } logger.info("Result of login:" + loginSucceeded); return loginSucceeded; }
commit 方法可尝试将使用 login 方法创建的委托人添加到主题中。也会对每个已配置的 LoginModule 调用此方法,并按顺序执行。对于每个 LoginModule 有关提交是否成功的信息都会得到存储。
如果对 LoginModule 的任何提交失败,那么会对每个已配置的 LoginModule 调用 abort 方法。abort 方法会从主题中将该 LoginModule 的委托人删除,实际上就是回滚操作。
logout 方法尝试将用户从系统中注销。该方法还可以重置主题,以便不再存储与其相关联的委托人。
MBean 是 JMX(Java 管理扩展包)的结构,是一类特殊的 JavaBean。WebLogic 提供了工具 WebLogic MBeanMaker,可以根据 MBean 定义文件(MDF)创建 MBean 类型。MDF 文件是描述 MBean 类型的 XML 文件,其中包括了配置安全提供程序的属性,如名字、描述和实现类等。
本例中的 MDF 是位于 src 文件夹下的 DBUserAuthenticator.xml。我们把数据库连接地址配置在了该文件中作为 MBean 属性,用户可修改成自己的数据库 URL。
图 13 展示了 MDF 中的 DbURL 属性
打开根目录下的 build.xml,我们使用 ant 命令来执行 MBeanMaker 工具。首先配置环境变量,注意 JDK 和 WebLogic 的安装目录。
图 14 build 文件中环境变量
运行 build 文件 all 命令,构建成功后自动把生成的 wldbuserprovider.jar 部署到/Oracle_Home/wlserver/server/lib/mbeantypes 下。所有自己定义的安全提供程序都应该部署在这个目录下,服务器启动后会将此处的扩展程序加载到 runtime。
图 15 新建安全提供程序
图 16 WL 中全局角色申明
回页首
安全验证,是每个企业级产品都需要面对的问题。几乎所有企业都已经有独立的身份安全验证系统的存在。企业用户也希望能够更多的使用这些既有信息,使各系统执行整齐划一的安全标准,同时减少软件开发的成本。由于 ODM 产品遵循 J2EE 标准的安全规范,使得这样的整合变得尤其容易。IBM WebSphere 与 Oracle WebLogic 作为使用最广泛的两大应用服务器,也都已经开放了不同接口供用户集成不同类型的安全验证数据源。本文中的两个示例,不光局限于 ODM 的产品,作为其它企业应用程序的整合,也可以作为参考。