Spring Boot场景下如何基于用户名密码实现Kerberos认证并获取SPNEGO令牌
方案合理性判断
该方案完全合理,适配你当前的场景限制:
- 用户工作站无Kerberos相关配置,无法走浏览器原生SPNEGO自动认证流程,将Kerberos认证逻辑收至服务端代理执行是最优选择,整个流程浏览器仅需处理普通接口请求和响应,无额外感知
- 核心逻辑无安全漏洞,只要保证传输层全程走SSL加密,即可避免用户明文凭证泄露
- 后续交互时前端仅需要将返回的SPNEGO令牌放入请求头即可,适配常规前后端交互模式
Java 环境实现方案
不需要调用系统外部的kinit命令,直接使用JDK内置的JAAS和GSS-API即可实现用户名密码换取SPNEGO令牌的逻辑,步骤如下:
第一步:基础配置
首先配置Kerberos基础参数,可以在Spring Boot启动时设置系统属性,无需额外的krb5.conf配置文件:
// 启动类中加入配置 @SpringBootApplication public class KrbServiceApplication { public static void main(String[] args) { // 替换为你的实际Kerberos配置 System.setProperty("java.security.krb5.realm", "YOUR_REALM"); System.setProperty("java.security.krb5.kdc", "YOUR_KDC_ADDRESS"); // 调试时可开启为true打印Kerberos交互日志 System.setProperty("sun.security.krb5.debug", "false"); // 禁用默认的JAAS配置文件路径,直接用代码指定配置 System.setProperty("java.security.auth.login.config", "jaas.conf"); SpringApplication.run(KrbServiceApplication.class, args); } }
然后在项目resources目录下创建jaas.conf配置文件,内容如下:
KrbUserLogin { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=false useTicketCache=false storeKey=true; };
第二步:令牌生成代码
封装工具类实现用户名密码生成SPNEGO令牌的逻辑:
import javax.security.auth.callback.*; import javax.security.auth.login.LoginContext; import javax.security.auth.Subject; import org.ietf.jgss.*; import java.security.PrivilegedAction; import java.util.Base64; @Component public class SpnegoTokenGenerator { /** * 生成SPNEGO令牌 * @param username 用户登录名 * @param password 用户密码 * @param serviceSpn 目标服务SPN,格式为http/service@REALM * @return Base64编码的SPNEGO令牌 * @throws Exception 认证或令牌生成失败时抛出 */ public String generateToken(String username, String password, String serviceSpn) throws Exception { // 回调处理器,传递用户名密码给Kerberos登录模块 CallbackHandler callbackHandler = callbacks -> { for (Callback cb : callbacks) { if (cb instanceof NameCallback) { ((NameCallback) cb).setName(username); } else if (cb instanceof PasswordCallback) { ((PasswordCallback) cb).setPassword(password.toCharArray()); } } }; // 用户登录Kerberos获取TGT LoginContext loginContext = new LoginContext("KrbUserLogin", callbackHandler); loginContext.login(); Subject userSubject = loginContext.getSubject(); // 以用户身份请求目标服务的TGS,封装为SPNEGO令牌 return Subject.doAs(userSubject, (PrivilegedAction<String>) () -> { try { GSSManager gssManager = GSSManager.getInstance(); // SPNEGO协议OID固定为1.3.6.1.5.5.2 Oid spnegoOid = new Oid("1.3.6.1.5.5.2"); GSSName serviceName = gssManager.createName(serviceSpn, GSSName.NT_HOSTBASED_SERVICE); GSSContext gssContext = gssManager.createContext(serviceName, spnegoOid, null, GSSContext.DEFAULT_LIFETIME); // 不需要双向认证可关闭 gssContext.requestMutualAuth(false); byte[] tokenBytes = gssContext.initSecContext(new byte[0], 0, 0); gssContext.dispose(); return Base64.getEncoder().encodeToString(tokenBytes); } catch (GSSException e) { throw new RuntimeException("SPNEGO令牌生成失败", e); } }); } }
第三步:前后端交互说明
- 前端登录接口成功获取到Base64编码的SPNEGO令牌后,后续所有请求在
Authorization请求头中携带Negotiate {令牌内容}即可 - 服务端可以直接使用Spring Security内置的SPNEGO认证过滤器,配合你已经持有的keytab完成令牌校验
优化建议
- 可以将用户登录后获取的
Subject对象缓存一段时间,缓存过期时间和Kerberos TGT的默认过期时间对齐,避免每次请求都重新向KDC发起认证,降低KDC压力同时提升接口响应速度 - 登录接口添加请求频率和失败次数限制,避免Kerberos账户被暴力破解
内容的提问来源于stack exchange,提问作者beerlogb
相关产品推荐
相关产品推荐

