注意:下面的答案仅对 Java EE 5 有效。正如在其他答案之一中引起我注意的那样,Java EE 6 确实支持这一点。因此,如果您使用的是 Java EE 6,请不要阅读此答案,而是阅读其他相关答案。
根据我自己的研究和对这个问题的回答,我找到了以下答案:虽然 JAAS 是一个标准接口,但在各种应用服务器中编写、部署和集成 JAAS Realm + LoginModule 并没有统一的方法。
Glassfish v2 要求您扩展它自己的一些内部类,这些类自己实现 LoginModule 或 Realm。但是,您不能自定义整个登录过程,因为 LoginModule 接口的许多方法在 Glassfish 的超类中都标记为 final。自定义 LoginModule 和 Realm 类必须放在 AS 类路径中(不是应用程序的),并且必须手动注册领域(不能从 .war 部署)。
Tomcat 的情况似乎好一些,它可以让您完全编写自己的 Realm 和 LoginModule,然后使用自己的 JAASRealm 将它们配置到应用程序服务器中(这会将实际工作委托给您的 Realm 实现和登录模块)。但是,即使是 tomcat 也不允许从您的 .war 部署您的自定义领域。
请注意,显示我的结果的所有应用程序服务器似乎都无法充分利用所有 JAAS 回调。它们似乎都只支持基本的用户名+密码方案。如果您需要比这更复杂的东西,那么您将需要找到一个不受 Java EE 容器管理的解决方案。
作为参考,并且因为我的问题在 cmets 中被要求,这里是我为 GlassfishV2 编写的代码。
首先,这是 Realm 的实现:
public class WebserviceRealm extends AppservRealm {
private static final Logger log = Logger.getLogger(WebserviceRealm.class.getName());
private String jaasCtxName;
private String hostName;
private int port;
private String uri;
@Override
protected void init(Properties props) throws BadRealmException, NoSuchRealmException {
_logger.info("My Webservice Realm : init()");
// read the configuration properties from the user-supplied properties,
// use reasonable default values if not present
this.jaasCtxName = props.getProperty("jaas-context", "myWebserviceRealm");
this.hostName = props.getProperty("hostName", "localhost");
this.uri = props.getProperty("uri", "/myws/EPS");
this.port = 8181;
String configPort = props.getProperty("port");
if(configPort != null){
try{
this.port = Integer.parseInt(configPort);
}catch(NumberFormatException nfe){
log.warning("Illegal port number: " + configPort + ", using default port (8181) instead");
}
}
}
@Override
public String getJAASContext() {
return jaasCtxName;
}
public Enumeration getGroupNames(String string) throws InvalidOperationException, NoSuchUserException {
List groupNames = new LinkedList();
return (Enumeration) groupNames;
}
public String getAuthType() {
return "My Webservice Realm";
}
public String getHostName() {
return hostName;
}
public int getPort() {
return port;
}
public String getUri() {
return uri;
}
}
然后是 LoginModule 实现:
public class WebserviceLoginModule extends AppservPasswordLoginModule {
// all variables starting with _ are supplied by the superclass, and must be filled
// in appropriately
@Override
protected void authenticateUser() throws LoginException {
if (_username == null || _password == null) {
throw new LoginException("username and password cannot be null");
}
String[] groups = this.getWebserviceClient().login(_username, _password);
// must be called as last operation of the login method
this.commitUserAuthentication(groups);
}
@Override
public boolean commit() throws LoginException {
if (!_succeeded) {
return false;
}
// fetch some more information through the webservice...
return super.commit();
}
private WebserviceClient getWebserviceClient(){
return theWebserviceClient;
}
}
最后,在 Realm 中必须绑定到 LoginModule。这是在 JAAS 配置文件级别完成的,在 glassfish v2 中位于 yourDomain/config/login.conf 中。在该文件的末尾添加以下行:
myWebserviceRealm { // use whatever String is returned from you realm's getJAASContext() method
my.auth.login.WebserviceLoginModule required;
};
这就是我在 glassfish 上工作的原因。同样,这个解决方案不能跨应用服务器移植,但据我所知,没有现成的可移植解决方案。