Kerberos身份验证错误“请求是重播34”

时间:2019-01-07 04:47:57

标签: java security jetty kerberos

首先,我使用以下示例代码生成Kerberos协商令牌:

package com.onenew.http.kerberos.test;
import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.apache.commons.codec.binary.Base64;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;

public class App {
    private static final String loginContextName = "xyz";
    private static final String userName = "username@EXAMPLE.COM";
    private static final String targetServerSpn = "server/EXAMPLE1.COM@EXAMPLE.COM";

    public static void main(String[] args) {
        try {
            System.setProperty("java.security.auth.login.config", "/var/jaas.conf");
            Oid krb5MechOid = new Oid("1.2.840.113554.1.2.2");
            Oid krb5PrincipalNameOid = new Oid("1.2.840.113554.1.2.2.1");
            GSSManager manager = GSSManager.getInstance();
            LoginContext lc = new LoginContext(loginContextName);
            lc.login();
            Subject subject = lc.getSubject();
            GSSCredential clientGssCreds = (GSSCredential) Subject.doAs(subject,
                    new CreateClientGSSCredentialAction(krb5MechOid, krb5PrincipalNameOid, manager));

            // create target server SPN
            GSSName gssServerName = manager.createName(targetServerSpn, krb5PrincipalNameOid);

            GSSContext clientContext = manager.createContext(gssServerName, krb5MechOid,
                    clientGssCreds, GSSContext.DEFAULT_LIFETIME);
            // optional enable GSS credential delegation
            clientContext.requestCredDeleg(true);

            byte[] spnegoToken = new byte[0];

            // create a SPNEGO token for the target server
            spnegoToken = clientContext.initSecContext(spnegoToken, 0, spnegoToken.length);
            String token = Base64.encodeBase64String(spnegoToken);
            System.out.println("Base64 Token:"+token);
            clientContext.dispose();

        }catch (GSSException e) {
            System.out.println("GSSException");
            System.out.println(e.getMessage());
        } catch (PrivilegedActionException e) {
            System.out.println("PrivilegedActionException");
            System.out.println(e.getMessage());
        } catch (LoginException e) {
            System.out.println("LoginException");
            System.out.println(e.getMessage());
        }

    }

    private static final class CreateClientGSSCredentialAction implements PrivilegedExceptionAction<GSSCredential> {
        private Oid krb5MechOid;
        private Oid krb5PrincipalNameOid;
        private GSSManager manager;

        private CreateClientGSSCredentialAction(Oid krb5MechOid, Oid krb5PrincipalNameOid, GSSManager manager) {
            this.krb5MechOid = krb5MechOid;
            this.krb5PrincipalNameOid = krb5PrincipalNameOid;
            this.manager = manager;
        }

        public GSSCredential run() throws GSSException, Exception {
            try {
                GSSName gssClientName = manager.createName(userName, krb5PrincipalNameOid);
                GSSCredential gssClientCred = manager.createCredential(gssClientName,
                        GSSCredential.DEFAULT_LIFETIME, krb5MechOid, GSSCredential.INITIATE_ONLY);
                return gssClientCred;
            } catch (GSSException e) {
                System.out.println("run method GSSException");
                System.out.println(e.getMessage());
            } catch (Exception e) {
                System.out.println("run method Exception");
                System.out.println(e.getMessage());
            }
            return null;
        }
    }
}

然后我使用生成的令牌请求由Jetty和Jersey创建的REST服务,在REST服务上有一个从ContainerRequestFilter实现的KerberosFilter,下面是代码:

package com.onenew.rest.security.filter;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Set;

import javax.ws.rs.core.Response;
import io.confluent.kafka.schemaregistry.rest.SchemaRegistryConfig;
import io.confluent.kafka.schemaregistry.storage.SchemaRegistry;
import io.confluent.rest.RestConfigException;
import org.apache.cxf.common.security.SimplePrincipal;
import org.apache.cxf.common.security.SimpleSecurityContext;
import org.apache.cxf.common.util.Base64Exception;
import org.apache.cxf.common.util.Base64Utility;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class KafkaKerberosFilter implements ContainerRequestFilter {
    private static final Logger log = LoggerFactory.getLogger(KafkaKerberosFilter.class);

    private static String REALM="EXAMPLE.COM";
    private static final String AUTHENTICATION_SCHEME = "Negotiate";
    private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
    private static final String KERBEROS_PRINCIPAL_OID="1.2.840.113554.1.2.2.1";

    private String loginContextName = "xyzService";
    private String servicePrincipalName;

    public KafkaKerberosFilter() {

    }

    public void filter(ContainerRequestContext requestContext) throws IOException {
        // Get the Authorization header from the request
        String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
        if(authorizationHeader==null||authorizationHeader.length()==0){
            String errorMsg="invalid authorization header";
            log.error(errorMsg);
            abortWithUnauthorized(requestContext,errorMsg);
            return;
        }
        String[] authPair = authorizationHeader.split(" ");
        if (authPair.length != 2 || !AUTHENTICATION_SCHEME.equalsIgnoreCase(authPair[0])) {
            String errorMsg="Negotiate Authorization scheme is expected";
            log.error(errorMsg);
            abortWithUnauthorized(requestContext,errorMsg);
            return;
        }
        log.debug("Base64 Token:"+authPair[1]);
        byte[] serviceTicket;
        try{
            serviceTicket = Base64Utility.decode(authPair[1]);
            log.debug("Decoded Token:"+serviceTicket.toString());
        }catch(Base64Exception e){
            String errorMsg="can't decode kerberos ticket";
            log.error(errorMsg+" "+authPair[1]);
            abortWithUnauthorized(requestContext,errorMsg);
            return;
        }
        try{
            Subject serviceSubject = loginAndGetSubject();
            GSSContext gssContext = createGSSContext(requestContext);
            Subject.doAs(serviceSubject, new ValidateServiceTicketAction(gssContext, serviceTicket));
            if (!gssContext.getCredDelegState()) {
                gssContext.dispose();
                gssContext = null;
            }

        }catch(LoginException e){
            String errorMsg="LoginContext can not be initialized";
            log.error(errorMsg+" "+loginContextName);
            abortWithUnauthorized(requestContext,errorMsg);
            return;
        }catch (GSSException e) {
            String errorMsg="GSS API exception";
            log.error(errorMsg+" "+e.getMessage());
            abortWithUnauthorized(requestContext,errorMsg);
            return;
        } catch (PrivilegedActionException e) {
            String errorMsg="PrivilegedActionException";
            log.error(errorMsg+" "+e.getMessage());
            abortWithUnauthorized(requestContext,errorMsg);
            return;
        }
    }

    private void abortWithUnauthorized(ContainerRequestContext requestContext,String error) {

        // Abort the filter chain with a 401 status code response
        // The WWW-Authenticate header is sent along with the response
        requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                .header(HttpHeaders.WWW_AUTHENTICATE, AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\",\nerror_description=\""+error+"\"").build());
    }

    protected Subject loginAndGetSubject() throws LoginException {

        // The login without a callback can work if
        // - Kerberos keytabs are used with a principal name set in the JAAS config
        // - Kerberos is integrated into the OS logon process
        //   meaning that a process which runs this code has the
        //   user identity
        LoginContext lc = new LoginContext(loginContextName);
        lc.login();
        Subject subject = lc.getSubject();
        return subject;
    }

    protected GSSContext createGSSContext(ContainerRequestContext requestContext) throws GSSException {
        Oid principalOid = new Oid(KERBEROS_PRINCIPAL_OID);
        Oid oid = new Oid(KERBEROS_OID);
        GSSManager gssManager = GSSManager.getInstance();
        String spn = getCompleteServicePrincipalName(requestContext);
        GSSName gssService = gssManager.createName(spn,principalOid);
        return gssManager.createContext(gssService,
                   oid, null, GSSContext.DEFAULT_LIFETIME);
    }

    protected String getCompleteServicePrincipalName(ContainerRequestContext requestContext) {
        String name = servicePrincipalName == null
            ? "server/" + requestContext.getUriInfo().getBaseUri().getHost() : servicePrincipalName;
            name += "@" + REALM;
        log.debug("Service Principal Name:"+name);
        return name;
    }

    public void setServicePrincipalName(String servicePrincipalName) {
        this.servicePrincipalName = servicePrincipalName;
    }

    private static final class ValidateServiceTicketAction implements PrivilegedExceptionAction<byte[]> {
        private final GSSContext context;
        private final byte[] token;

        private ValidateServiceTicketAction(GSSContext context, byte[] token) {
            this.context = context;
            this.token = token;
        }

        public byte[] run() throws GSSException {
            try{
                return context.acceptSecContext(token, 0, token.length);
            }catch(GSSException e){
                log.error("PrivilegedExceptionAction run error:"+e.getMessage());
                throw e;
            }
        }
    }
}

带有生成令牌的第一个请求是成功。但是以下具有相同令牌的请求全部失败。 ValidateServiceTicketAction类的方法运行抛出GSSException,它表示“在GSS-API级别未指定失败(机制级别:请求是重播(34))”。我用谷歌搜索,找不到解决方案,大多数答案都说时间必须同步,但是我已经在客户端,REST服务和KDC上一直保持同步。

0 个答案:

没有答案