Java Web 应用程序:使用自定义领域

发布于 2024-07-17 03:14:55 字数 237 浏览 10 评论 0原文

我正在编写一个java Web应用程序,需要通过Web服务执行登录。 当然,我正在使用的应用程序服务器(glassfish v2)提供的所有领域都无法做到这一点。 因此我必须自己写。 然而,我编写的领域实现似乎完全依赖于 glassfish,并且不能按原样在任何其他应用程序服务器中使用。

是否有任何标准或广泛支持的方法来实现自定义领域? 是否可以以任何方式从 .war 部署该领域,或者它总是需要从服务器自己的类路径加载?

I'm writing a java web application which need to perform login through a webservice. Of course, none of the realms supplied with the application server I'm using (glassfish v2) can do the trick. I therefore had to write my own. It seems however, that the realm implementation that I wrote is completely tied to glassfish and cannot be used as is in any other application servers.

Is there any standard or widely supported way to implement a custom Realm? Is it in any way possible to deploy that realm from a .war, or does it always need to be loaded from the server's own classpath?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

守望孤独 2024-07-24 03:14:55

注意:下面的答案仅对 Java EE 5 有效。正如在其他答案之一中引起我注意的那样,Java EE 6 确实支持这一点。 因此,如果您使用的是 Java EE 6,请不要阅读此答案,而应阅读其他相关答案。

根据我自己的研究和对这个问题的回答,我发现答案如下:虽然JAAS是一个标准接口,但在各种应用服务器中编写、部署和集成JAAS Realm + LoginModule并没有统一的方法。

Glassfish v2 要求您扩展它自己的一些内部类,这些类本身实现 LoginModule 或 Realm。 但是,您无法自定义整个登录过程,因为 LoginModule 接口的许多方法在 Glassfish 的超类中被标记为最终方法。 自定义 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;
};

这就是我在 glassfish 上工作的原因。 同样,该解决方案不可跨应用程序服务器移植,但据我所知,目前还没有可移植的解决方案。

NOTE: The answer below is only valid for Java EE 5. As was brought to my attention in one of the other answers, Java EE 6 does support this. So if you're using Java EE 6, don't read this answer, but read the other relevant answer.

From my own research and from the responses to this question the answer I found is the following: Although JAAS is a standard interface, there is no uniform way to write, deploy and integrate a JAAS Realm + LoginModule in various application servers.

Glassfish v2 requires you to extend some of its own internal classes that implement LoginModule or Realm themselves. You can however not customize the whole login process because many methods of the LoginModule interface are marked final in Glassfish's superclass. The custom LoginModule and Realm classes must be placed in the AS classpath (not the application's), and the realm must be manually registered (no deployment from the .war possible).

The situation seems to be a bit better for Tomcat, which will let you code your own Realm and LoginModule completely, and then configure them into the application server using its own JAASRealm (which will delegate the actual work to your implementations of Realm and LoginModule). However, even tomcat doesn't allow deploying your custom realm from your .war.

Note that none of the Application Servers that showed up my results seem to be able to take full advantage of all the JAAS Callbacks. All of them seem to only support the basic username+password scheme. If you need anything more complicated than that, then you will need to find a solution that is not managed by your Java EE container.

For reference, and because it was asked for in the comments to my question, here is the code I wrote for GlassfishV2.

First of all, here's the Realm implementation:

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;
}
}

And then the LoginModule implementation:

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;
}
}

finally, in the Realm has to be tied to the LoginModule. This is done at the JAAS configuration file level, which in glassfish v2 lies at yourDomain/config/login.conf. Add the following lines at the end of that file:

myWebserviceRealm { // use whatever String is returned from you realm's getJAASContext() method
    my.auth.login.WebserviceLoginModule required;
};

This is what got things working for me on glassfish. Again, this solution is not portable across application servers, but as far as I can tell, there is no existing portable solution.

原来是傀儡 2024-07-24 03:14:55

是否有任何标准或广泛支持的方式来实现自定义
领域? 是否有可能以任何方式从 .war 部署该领域,或者
它总是需要从服务器自己的类路径加载吗?

绝对有一种标准方法来实现自定义领域,或者更一般地说,自定义身份验证模块。 这可以通过 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,身份验证模块(“领域”)确实可以从 .war 加载。 我不能 100% 确定规范是否保证这一点,但在我测试的 4 个服务器(GlassFish、WebLogic、Geronimo 和 JBoss AS)中,它们都支持这一点。 不幸的是,Geronimo 在其编程注册中存在某种竞争条件,因此您需要通过执行两次热部署来实现一种丑陋的解决方法,但最终它确实从 .war 加载了模块。

至于专有机制,至少JBoss AS始终支持从.war或.ear加载模块(例如org.jboss.security.auth.spi.AbstractServerLoginModule的子类)。

我最近写了一篇关于此主题的博客文章,其中包含更多详细信息。

Is there any standard or widely supported way to implement a custom
Realm? Is it in any way possible to deploy that realm from a .war, or
does it always need to be loaded from the server's own classpath?

There absolutely is a standard way to implement a custom Realm, or in more general terms a custom authentication module. This can be done via the JASPIC/JASPI/JSR 196 SPI/API. JASPIC is a standard part of any full Java EE 6 implementation, but unfortunately not part of the Java EE 6 Web Profile.

However, despite JASPIC being a part of Java EE 6, it's not being optimally supported by vendors. GlassFish and WebLogic seem to have very good implementations, JBoss AS and Geronimo are a bit more problematic. The lead engineer from JBoss on this topic (Anil Saldhana) has even stated that he refuses to activate JASPIC by default for the moment. A few of the most severe bugs in Jboss AS 7.1 have been recently fixed, but as there are no public releases of JBoss 7.1.x scheduled anymore and JBoss AS 7.2 is still some time away it means as of now at least on JBoss JASPIC is troublesome.

Another unfortunate issue is that the actual authentication module may be standardized, but there's no declarative way (read XML file) to configure it that's standardized.

Is it in any way possible to deploy that realm from a .war, or does it
always need to be loaded from the server's own classpath?

With JASPIC, the authentication module ('realm') can indeed be loaded from a .war. I'm not 100% sure whether this is guaranteed by the spec, but of the 4 servers I tested (GlassFish, WebLogic, Geronimo and JBoss AS), they all supported this. Geronimo unfortunately has some kind of race condition in its programmatic registration, so you need an ugly workaround by doing a hot deploy twice, but it in the end if does load the module from the .war.

As of the proprietary mechanisms, at least JBoss AS has always supported loading the module (e.g. a subclass of org.jboss.security.auth.spi.AbstractServerLoginModule) from the .war or .ear.

I wrote a blog post about this topic recently that has some more details.

扬花落满肩 2024-07-24 03:14:55

您永远无法从 WAR 部署领域,因为该领域不是应用程序工件,而是容器工件(因此称为“基于容器的安全性”)。 您可以将应用程序配置为使用容器提供的特定领域,但应用程序本身无法提供该领域。

也就是说,虽然所有容器都不同,并且这些领域不可移植,但如果您正在寻求可移植性,简单的常识将减少与容器集成所需的一点粘合代码的差异。

You can never deploy a realm from a WAR because the realm is NOT an Application Artifact, it is a Container Artifact (thus the phrase "container based security"). You can configure your app to use a specific realm as provided by the container, but the application can not provide this itself.

That said, while all of the containers are different, and these realms are NOT portable, simple common sense will reduce the differences to the little bit of glue code necessary to integrate with the container, if you're looking for portability.

也只是曾经 2024-07-24 03:14:55

快速浏览一下 Suns 文档后,您似乎必须编写一个自定义 LoginModule 来扩展其应用程序服务器特定类。 这对我来说似乎有点倒退,也是 Glassfish 的局限性。

如果您想让它更加可移植,我建议将大部分实现放在针对标准 JavaEE 接口开发的自定义 LoginModule 中,然后拥有一个特定于 Glassfish 的薄实现层,该实现层委托给标准的可移植实现。

Having has a quick look at Suns documentation it looks like you will have to write a custom LoginModule which extends their app server specific class. This seems a little backwards to me and a limitation of Glassfish.

If you want to make this more portable, I'd suggest putting the bulk of the implementation in a custom LoginModule developed against the standard JavaEE interface(s) and then having a thin implementation layer specific to Glassfish that delegates to the standard, portable implementation.

花间憩 2024-07-24 03:14:55

请查看 Sun 关于此主题的文章

我自己实际上从未这样做过,但我非常有信心每个 AS 都为您提供了在其上注册新领域(安全域)的选项。

它可能不会 100% 可移植,并且对于每个 AS,您可能需要不同的配置 XML,但基本上,代码没有任何不同的理由。

Check out Sun's article on this subject.

I never actually did this myself, but I'm pretty confident each AS gives you an option to register new realms (security domains) on it.

It probably won't be 100% portable, and for each AS you might need a different config XML, but basically, there is no reason for the code to be any different.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文