是否可以在不将 jar 添加到 lib 文件夹的情况下在 tomcat 中创建自定义领域?

Is it possible to create custom realm in tomcat without adding jar to lib folder?

我想制作带有自定义身份验证的网络应用程序。但是我必须为此将 Realm 添加到 server.xml 中。这需要我将 jar 添加到 tomcat lib 文件夹。我可以将所有内容都保存在我的 war 文件中吗?如何?

谢谢!

不幸的是,无法从 Tomcat 中的 WAR 部署自定义领域。必须可以在 CATALINA_HOME 中访问自定义领域实现。它被视为容器工件,而不是应用程序工件。

我只能想到这三个选项:

选项 1:移动到另一个 Web 应用程序服务器

至少 GlassfishJBoss AS 支持从 WAR.[=13 部署的自定义领域=]

选项 2:不使用自定义领域

Tomcat 8 现在允许自定义凭证处理。如果可能足以满足您需要实现的目标,而不是使用自定义 realmn 解决方案:

Apache Tomcat 8.0.15 Available

[...]

Add pluggable password derivation support to the Realms via the new CredentialHandler interface.

http://www.tomcatexpert.com/blog/2014/11/14/apache-tomcat-8015-available

https://tomcat.apache.org/tomcat-8.0-doc/config/credentialhandler.html

选项 3:处理它

如果您的解决方案已经按预期运行,您可能希望保留它并直接处理部署问题。

另一个不错的选择是使用 Tomcat 的库存 JAASRealm (org.apache.catalina.realm.JAASRealm) 来实现自定义 JAAS LoginModule。这种方法的好处是 JAASRealm 可以通过类路径来源的 configFile 值进行配置,以便所有内容都捆绑在 WAR 文件中。来自 https://tomcat.apache.org/tomcat-9.0-doc/config/realm.html#JAAS_Realm_-_org.apache.catalina.realm.JAASRealm 关于 configFile 领域属性:

The name of a JAAS configuration file to use with this Realm. It will be searched for using ClassLoader#getResource(String) so it is possible for the configuration to be bundled within a web application. If not specified, the default JVM global JAAS configuration will be used.

以下是所有部分的简单示例:

User.java

package com.company.jaas;

import java.security.Principal;

public class User implements Principal {
  private final String username;

  public User(String username) {
    this.username = username;
  }

  public String getUsername() {
    return username;
  }

  @Override
  public String getName() {
    return getUsername();
  }

  @Override
  public String toString() {
    return "User{" + "username=" + username + '}';
  }
}

Role.java

package com.company.jaas;

import java.security.Principal;

public class Role implements Principal {
  private final String name;

  public Role(String name) {
    this.name = name;
  }

  @Override
  public String getName() {
    return name;
  }

  @Override
  public String toString() {
    return "Role{" + "name=" + name + '}';
  }
}

DemoLoginModule.java

package com.company.jaas;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class DemoLoginModule implements LoginModule {
  private CallbackHandler handler;
  private Subject subject;
  private User user;
  private List<Role> roles;

  @Override
  public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
    this.handler = callbackHandler;
    this.subject = subject;
  }

  @Override
  public boolean login() throws LoginException {
    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("login");
    callbacks[1] = new PasswordCallback("password", true);

    try {
      handler.handle(callbacks);
      String username = ((NameCallback) callbacks[0]).getName();
      String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());

      // todo: perform real validation here (using any desired mechanism)
      if ("admin".equalsIgnoreCase(username) && "password".equals(password)) {
        user = new User(username);
        roles = List.of(new Role("admin"));
        return true;
      }

      throw new FailedLoginException("Authentication failed");
    }
    catch (IOException | UnsupportedCallbackException ex) {
      throw new LoginException(ex.getMessage());
    }
  }

  @Override
  public boolean commit() throws LoginException {
    subject.getPrincipals().add(user);
    subject.getPrincipals().addAll(roles);
    return true;
  }

  @Override
  public boolean abort() throws LoginException {
    return false;
  }

  @Override
  public boolean logout() throws LoginException {
    subject.getPrincipals().remove(user);
    subject.getPrincipals().removeAll(roles);
    return true;
  }
}

jaas.config

DemoAuth {
  com.company.jaas.DemoLoginModule required ;
};

以上所有内容都可以打包在一个 jar 中,并作为库包含在另一个应用程序中。该应用程序可以通过使用以下 tomcat 领域配置来使用 JAAS 支持的身份验证:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

  <security-role>
    <role-name>admin</role-name>
  </security-role>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>action</web-resource-name>
      <url-pattern>/admin/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>admin</role-name>
    </auth-constraint>
  </security-constraint>

  <login-config>
    <auth-method>FORM</auth-method>
    <form-login-config>
      <form-login-page>/WEB-INF/jsp/login.jsp</form-login-page>
      <form-error-page>/WEB-INF/jsp/login-error.jsp</form-error-page>       
    </form-login-config>
  </login-config>

</web-app>

context.xml

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/JaasDemo">
  <Realm appName="DemoAuth" 
    configFile="jaas.config" 
    className="org.apache.catalina.realm.JAASRealm" 
    userClassNames="com.company.jaas.User"
    roleClassNames="com.company.jaas.Role" /> 
</Context>

jaas.config 文件将在类路径中找到(在 Web 应用程序本地或在 jar 依赖项中)。任何受管理员角色保护的资源(即任何 url 模式 /admin/*)在我们简单的 DemoLoginModule 实现中通过 admin/password 凭据进行身份验证后就可以访问。