jjzjj

java - 使用 GSSManager 验证 Kerberos 票证

coder 2024-03-04 原文

我有以下代码:

public static void main(String args[]){
    try {
        //String ticket = "Negotiate YIGCBg...==";
        //byte[] kerberosTicket = ticket.getBytes();
        byte[] kerberosTicket = Base64.decode("YIGCBg...==");
        GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
        context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
        String user = context.getSrcName().toString();
        context.dispose();
    } catch (GSSException e) {
        e.printStackTrace();
    } catch (Base64DecodingException e) {
        e.printStackTrace();
    }
}

当然失败了。这是异常(exception):
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
我不知道我该怎么做才能解决这个问题。老实说,我不太了解 Kerberos。

我通过发送带有适当标题的 401 WWW-Authenticate 获得了这张票以“协商”为值。浏览器立即再次发出相同的请求 authorization包含此票证的 header 。

我希望我可以验证票证并确定用户是谁。

我需要 key 表文件吗?如果是这样,我将在什么凭据下运行它?我正在尝试使用 Kerberos 票证对网站进行身份验证。凭据是来自 IIS 的凭据吗?

我错过了什么?

更新 1
从 Michael-O 的回复中,我做了更多的谷歌搜索,发现 this article ,这让我找到了 this article .

table 3 , 我找到了 1.3.6.1.5.5.2 SPNEGO .

我现在按照第一篇文章中的示例将其添加到我的凭据中。这是我的代码:
public static void main(String args[]){
    try {            
        Oid mechOid = new Oid("1.3.6.1.5.5.2");

        GSSManager manager = GSSManager.getInstance();

        GSSCredential myCred = manager.createCredential(null,
                GSSCredential.DEFAULT_LIFETIME,
                mechOid,
                GSSCredential.ACCEPT_ONLY);

        GSSContext context = manager.createContext(myCred);

        byte[] ticket = Base64.decode("YIGCBg...==");
        context.acceptSecContext(ticket, 0, ticket.length);
        String user = context.getSrcName().toString();
        context.dispose();
    } catch (GSSException e) {
        e.printStackTrace();
    } catch (Base64DecodingException e) {
        e.printStackTrace();
    }
}

但是现在代码在 createCredential 上失败了出现此错误:
GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos credentails)

这是全票:YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==

最佳答案

从 Java 验证 SPNEGO 票证是一个有点复杂的过程。这是一个简短的概述,但请记住,该过程可能存在大量陷阱。您确实需要了解 Active Directory、Kerberos、SPNEGO 和 JAAS 如何成功诊断问题。

在开始之前,请确保您知道 Windows 域的 kerberos 领域名称。出于这个答案的目的,我假设它是 MYDOMAIN .您可以通过运行 echo %userdnsdomain% 获取领域名称。从 cmd 窗口。请注意,kerberos 区分大小写,并且领域几乎总是全部大写。

步骤 1 - 获取 Kerberos Keytab

为了让 kerberos 客户端访问服务,它请求代表该服务的服务主体名称 [SPN] 的票证。 SPN 通常源自机器名称和正在访问的服务类型(例如 HTTP/www.my-domain.com)。为了验证特定 SPN 的 kerberos 票证,您必须有一个 key 表文件,其中包含 Kerberos 域 Controller [KDC] 票证授予票证 [TGT] 服务和服务提供商(您)都知道的共享 secret 。

在 Active Directory 方面,KDC 是域 Controller ,共享 secret 只是拥有 SPN 的帐户的纯文本密码。 SPN 可以由 AD 中的计算机或用户对象拥有。

如果您要定义服务,则在 AD 中设置 SPN 的最简单方法是设置基于用户的 SPN,如下所示:

  • 在 AD 中创建一个密码不会过期的非特权服务帐户,例如SVC_HTTP_MYSERVER 带密码 ReallyLongRandomPass
  • 使用windows将服务SPN绑定(bind)到账号setspn公用事业。最佳实践是为主机的短名称和 FQDN 定义多个 SPN:
    setspn -U -S HTTP/myserver@MYDOMAIN SVC_HTTP_MYSERVER
    setspn -U -S HTTP/myserver.my-domain.com@MYDOMAIN SVC_HTTP_MYSERVER
    
  • 使用 Java 的 ktab 为帐户生成 key 表公用事业。
    ktab -k FILE:http_myserver.ktab -a HTTP/myserver@MYDOMAIN ReallyLongRandomPass
    ktab -k FILE:http_myserver.ktab -a HTTP/myserver.my-domain.com@MYDOMAIN ReallyLongRandomPass
    

  • 如果您尝试对绑定(bind)到计算机帐户或您无法控制的用户帐户的预先存在的 SPN 进行身份验证,则上述方法将不起作用。您需要从 ActiveDirectory 本身提取 key 表。 Wireshark Kerberos Page对此有一些很好的指示。

    第 2 步 - 设置您的 krb5.conf

    %JAVA_HOME%/jre/lib/security创建描述您的域的 krb5.conf。确保您在此处定义的领域与您为 SPN 设置的领域相匹配。如果不把文件放在JVM目录下,可以通过设置-Djava.security.krb5.conf=C:\path\to\krb5.conf来指向它在命令行上。

    例子:

    [libdefaults]
      default_realm = MYDOMAIN
    
    [realms]
      MYDOMAIN = {
        kdc = dc1.my-domain.com
        default_domain = my-domain.com
      }
    
    [domain_realm]
      .my-domain.com = MYDOMAIN
      my-domain.com = MYDOMAIN
    

    第 3 步 - 设置 JAAS login.conf

    您的 JAAS login.conf应该定义一个登录配置来设置 Krb5LoginModule 作为接受者。这是一个假设我们上面创建的 key 表在 C:\http_myserver.ktab 中的示例。 .通过设置 -Djava.security.auth.login.config=C:\path\to\login.conf 指向 JASS 配置文件在命令行上。

    http_myserver_mydomain {
      com.sun.security.auth.module.Krb5LoginModule required
      principal="HTTP/myserver.my-domain.com@MYDOMAIN"
      doNotPrompt="true" 
      useKeyTab="true" 
      keyTab="C:/http_myserver.ktab"
      storeKey="true"
      isInitiator="false";
    };
    

    或者,您可以在运行时生成 JAAS 配置,如下所示:
    public static Configuration getJaasKrb5TicketCfg(
        final String principal, final String realm, final File keytab) {
      return new Configuration() {
        @Override
        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
          Map<String, String> options = new HashMap<String, String>();
          options.put("principal",    principal);
          options.put("keyTab",       keytab.getAbsolutePath());
          options.put("doNotPrompt", "true");
          options.put("useKeyTab",   "true");
          options.put("storeKey",    "true");
          options.put("isInitiator", "false");
    
          return new AppConfigurationEntry[] {
            new AppConfigurationEntry(
              "com.sun.security.auth.module.Krb5LoginModule",
              LoginModuleControlFlag.REQUIRED, options)
          };
        }
      };
    }
    

    您将创建一个 登录上下文 对于这样的配置:
    LoginContext ctx = new LoginContext("doesn't matter", subject, null, 
      getJaasKrbValidationCfg("HTTP/myserver.my-domain.com@MYDOMAIN", "MYDOMAIN", 
        new File("C:/path/to/my.ktab")));
    

    第 4 步 - 接受票

    这有点随意,但总体思路是定义一个 PriviledgedAction,它使用票证执行 SPNEGO 协议(protocol)。请注意,此示例不检查 SPNEGO 协议(protocol)是否完整。例如,如果客户端请求服务器身份验证,您将需要返回由 acceptSecContext() 生成的 token 。在 HTTP 响应中的身份验证 header 中。
    public class Krb5TicketValidateAction implements PrivilegedExceptionAction<String> {
      public Krb5TicketValidateAction(byte[] ticket, String spn) {
        this.ticket = ticket;
        this.spn = spn;
      }
    
      @Override
      public String run() throws Exception {
        final Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
    
        GSSManager gssmgr = GSSManager.getInstance();
    
        // tell the GSSManager the Kerberos name of the service
        GSSName serviceName = gssmgr.createName(this.spn, GSSName.NT_USER_NAME);
    
        // get the service's credentials. note that this run() method was called by Subject.doAs(),
        // so the service's credentials (Service Principal Name and password) are already 
        // available in the Subject
        GSSCredential serviceCredentials = gssmgr.createCredential(serviceName,
          GSSCredential.INDEFINITE_LIFETIME, spnegoOid, GSSCredential.ACCEPT_ONLY);
    
        // create a security context for decrypting the service ticket
        GSSContext gssContext = gssmgr.createContext(serviceCredentials);
    
        // decrypt the service ticket
        System.out.println("Entering accpetSecContext...");
        gssContext.acceptSecContext(this.ticket, 0, this.ticket.length);
    
        // get the client name from the decrypted service ticket
        // note that Active Directory created the service ticket, so we can trust it
        String clientName = gssContext.getSrcName().toString();
    
        // clean up the context
        gssContext.dispose();
    
        // return the authenticated client name
        return clientName;
      }
    
      private final byte[] ticket;
      private final String spn;
    }
    

    然后要验证票证,您将执行以下操作。假设 ticket包含来自身份验证 header 的已经 base-64 解码的票证。 spn应该来自 Host如果格式为 HTTP/<HOST>@<REALM>,则 HTTP 请求中的 header .例如。如果Host标题是 myserver.my-domain.com然后 spn应该是 HTTP/myserver.my-domain.com@MYDOMAIN .
    public boolean isTicketValid(String spn, byte[] ticket) {
      LoginContext ctx = null;
      try {
        // this is the name from login.conf.  This could also be a parameter
        String ctxName = "http_myserver_mydomain";
    
        // define the principal who will validate the ticket
        Principal principal = new KerberosPrincipal(spn, KerberosPrincipal.KRB_NT_SRV_INST);
        Set<Principal> principals = new HashSet<Principal>();
        principals.add(principal);
    
        // define the subject to execute our secure action as
        Subject subject = new Subject(false, principals, new HashSet<Object>(), 
          new HashSet<Object>());
    
        // login the subject
        ctx = new LoginContext("http_myserver_mydomain", subject);
        ctx.login();
    
        // create a validator for the ticket and execute it
        Krb5TicketValidateAction validateAction = new Krb5TicketValidateAction(ticket, spn);
        String username = Subject.doAs(subject, validateAction);
        System.out.println("Validated service ticket for user " + username 
          + " to access service " + spn );
        return true;
      } catch(PriviledgedActionException e ) {
         System.out.println("Invalid ticket for " + spn + ": " + e);
      } catch(LoginException e) {
        System.out.println("Error creating validation LoginContext for " 
          + spn + ": " + e);
      } finally {
        try {
          if(ctx!=null) { ctx.logout(); }
        } catch(LoginException e) { /* noop */ }
      }
    
      return false;
    }
    

    关于java - 使用 GSSManager 验证 Kerberos 票证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25289231/

    有关java - 使用 GSSManager 验证 Kerberos 票证的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    5. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    8. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    10. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

      我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

    随机推荐