You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Waffle Java客户端:如何使用特定域用户认证Web服务

解决Tomcat上Spring应用访问Kerberos/NTLM认证OData服务的问题

我之前在处理Tomcat部署的Spring应用访问Kerberos认证的OData服务时遇到过几乎一模一样的问题,折腾了好几天终于搞定了,给你分享几个可行的方案和踩过的坑:

核心问题分析

你测试环境用NTCredentials能成功,是因为服务返回的WWW-Authenticate是NTLM;但客户环境返回Negotiate,意味着服务优先使用Kerberos认证,而NTCredentials只支持NTLM,所以直接失效。Waffle默认用当前进程的用户票据(比如启动Tomcat的账户),但Tomcat作为后台服务运行时没有交互登录用户,所以需要手动指定用户密码来获取Kerberos票据。

方案一:使用Waffle API手动指定用户认证

Waffle确实支持指定用户名密码完成Kerberos/NTLM认证,你之前尝试logonUser和impersonate没成功,大概率是没正确处理安全上下文和HttpClient的集成。以下是完整的实现代码:

1. 添加Waffle依赖

如果是Maven项目,在pom.xml中加入:

<dependency>
    <groupId>com.github.waffle</groupId>
    <artifactId>waffle-jna</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>com.github.waffle</groupId>
    <artifactId>waffle-httpclient</artifactId>
    <version>3.3.0</version>
</dependency>

2. 构建支持Kerberos的RestTemplate

import com.github.waffle.jna.WindowsAuthProviderImpl;
import com.github.waffle.jna.WindowsCredentialsHandle;
import com.github.waffle.windows.auth.IWindowsAuthProvider;
import com.github.waffle.windows.auth.IWindowsCredentialsHandle;
import org.apache.http.auth.AuthScope;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class KerberosRestTemplateFactory {

    public RestTemplate createRestTemplate(String domain, String username, String password, String serviceSpn) {
        // 初始化Waffle认证提供者
        IWindowsAuthProvider authProvider = new WindowsAuthProviderImpl();

        try (IWindowsCredentialsHandle credHandle = authProvider.logonUser(username, domain, password)) {
            // 绑定到目标服务的SPN(必须正确,否则Kerberos认证失败)
            authProvider.initializeSecurityContext(credHandle, serviceSpn);

            // 注册支持Negotiate(Kerberos/NTLM)的认证方案
            Registry<?> authSchemeRegistry = RegistryBuilder.create()
                    .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true))
                    .build();

            // 设置凭证提供者
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(AuthScope.ANY, new com.github.waffle.apache.http.NegotiateCredentials());

            // 构建HttpClient
            var httpClient = HttpClientBuilder.create()
                    .setDefaultAuthSchemeRegistry(authSchemeRegistry)
                    .setDefaultCredentialsProvider(credsProvider)
                    .build();

            // 配置RestTemplate
            var requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
            RestTemplate restTemplate = new RestTemplate();
            restTemplate.setRequestFactory(requestFactory);

            return restTemplate;
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialize Kerberos authenticated RestTemplate", e);
        }
    }
}

关键注意事项

  • SPN必须正确:目标OData服务的Service Principal Name(SPN)格式通常是HTTP/your-odata-host.your-domain.com@YOUR-DOMAIN.COM。可以在客户域控制器上用setspn -L <服务运行账户>命令查看目标服务已注册的SPN。
  • Tomcat运行权限:Tomcat的运行账户(比如域账户或本地账户)需要拥有允许模拟客户端和允许登录本地的权限,否则logonUser会失败。
  • 调试Kerberos:可以开启Waffle的调试日志,添加JVM参数:-Dwaffle.debug=true,帮助定位认证失败原因。

方案二:使用Java原生Kerberos API

如果Waffle的方案还是有问题,可以尝试Java原生的Kerberos登录API,直接获取用户票据后模拟请求:

1. 配置JAAS和Kerberos文件

  • 创建jaas.conf文件:
KerberosLogin {
    com.sun.security.auth.module.Krb5LoginModule required
    useKeyTab=false
    useTicketCache=false
    principal="USERNAME@YOUR-DOMAIN.COM"
    debug=true;
}
  • 确保krb5.ini(Windows)或krb5.conf(Linux)配置正确,指向客户的域控制器。

2. 代码实现

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
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 java.io.IOException;
import java.security.PrivilegedAction;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.ResponseEntity;

public class NativeKerberosClient {

    public String callODataService(String url, String domain, String username, String password) throws LoginException {
        // 设置系统属性
        System.setProperty("java.security.auth.login.config", "path/to/jaas.conf");
        System.setProperty("java.security.krb5.conf", "path/to/krb5.ini");
        System.setProperty("sun.security.krb5.debug", "true"); // 调试用

        // 创建登录上下文
        LoginContext lc = new LoginContext("KerberosLogin", new CallbackHandler() {
            @Override
            public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
                for (Callback cb : callbacks) {
                    if (cb instanceof NameCallback) {
                        ((NameCallback) cb).setName(username + "@" + domain.toUpperCase());
                    } else if (cb instanceof PasswordCallback) {
                        ((PasswordCallback) cb).setPassword(password.toCharArray());
                    }
                }
            }
        });

        // 登录获取Subject
        lc.login();
        Subject subject = lc.getSubject();

        // 使用Subject.doAs模拟用户请求
        return Subject.doAs(subject, (PrivilegedAction<String>) () -> {
            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> response = restTemplate.exchange(url, org.springframework.http.HttpMethod.GET, null, String.class);
            return response.getBody();
        });
    }
}

最后总结

  • 优先选择Waffle方案,它对Windows环境的Kerberos/NTLM协议支持更完善,能自动处理Negotiate的协议切换。
  • 认证失败的最常见原因是SPN配置错误,一定要确认目标服务的SPN和代码中指定的一致。
  • 如果Tomcat运行在Windows服务中,确保服务账户有足够的权限,必要时联系客户的AD管理员配置权限。

内容的提问来源于stack exchange,提问作者Jon C

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:46:53