我正在编写一个需要通过webservice登录的java Web应用程序.当然,我使用的应用程序服务器(glassfish v2)提供的领域都不能解决问题.因此,我必须自己写.但是,我写的领域实现似乎完全依赖于glassfish,不能像任何其他应用程序服务器那样使用.
是否有任何标准或广泛支持的方式来实现自定义Realm?是以任何方式从.war部署该领域,还是总是需要从服务器自己的类路径加载?
注意:下面的答案仅对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和LoginModule实现) .但是,即使tomcat也不允许从.war部署自定义领域.
请注意,没有显示我的结果的应用程序服务器似乎能够充分利用所有JAAS回调.所有这些似乎只支持基本的用户名+密码方案.如果您需要更复杂的东西,那么您需要找到一个不受Java EE容器管理的解决方案.
作为参考,并且因为在我的问题的评论中要求它,这是我为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; };
这就是玻璃鱼让我感觉有用的东西.同样,这个解决方案不能跨应用程序服务器移植,但据我所知,没有现有的便携式解决方案.
是否有任何标准或广泛支持的方式来实现自定义Realm?是以任何方式从.war部署该领域,还是总是需要从服务器自己的类路径加载?
绝对有一种实现自定义Realm的标准方法,或者更一般地说是自定义身份验证模块.这可以通过JASPIC/JASPI/JSR 196 SPI/API完成.JASPIC是任何完整Java EE 6实现的标准部分,但遗憾的是它不是Java EE 6 Web Profile的一部分.
然而,尽管JASPIC是Java EE 6的一部分,但供应商并未对其提供最佳支持.GlassFish和WebLogic似乎有很好的实现,JBoss AS和Geronimo有点问题.JBoss的主要工程师(Anil Saldhana)甚至声称他暂时拒绝激活 JASPIC .最近修复了Jboss AS 7.1中一些最严重的错误,但由于JBoss 7.1.x没有公开发布,JBoss AS 7.2还有一段时间了,这意味着至少在JBoss JASPIC上是麻烦.
另一个不幸的问题是实际的身份验证模块可能是标准化的,但是没有声明性的方式(读取XML文件)来配置它的标准化.
是以任何方式从.war部署该领域,还是总是需要从服务器自己的类路径加载?
使用JASPIC,验证模块('realm')确实可以从.war加载.我不是100%确定这是否由规范保证,而是我测试的4台服务器(GlassFish,WebLogic,Geronimo和JBoss AS),他们都支持这一点.遗憾的是,Geronimo在程序化注册中存在某种竞争条件,所以你需要通过两次热部署来实现一个丑陋的解决方案,但最后如果从.war加载模块的话.
从专有机制来看,至少JBoss AS始终支持从.war或.ear加载模块(例如org.jboss.security.auth.spi.AbstractServerLoginModule的子类).
我最近写了一篇关于这个话题的博客文章,里面有更多细节.