加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

java – 使用有效客户端证书时出现HttpClient 403错误

发布时间:2020-12-15 08:49:44 所属栏目:Java 来源:网络整理
导读:我正在尝试使用 Java在网站上自动执行某些任务.我有一个有效的客户端为该网站(当我使用Firefox登录时工作),但当我尝试使用http客户端登录时,我一直收到403错误.请注意,我希望我的信任商店信任任何东西(我知道它不安全,但此时我并不担心). 这是我的代码: Key
我正在尝试使用 Java在网站上自动执行某些任务.我有一个有效的客户端为该网站(当我使用Firefox登录时工作),但当我尝试使用http客户端登录时,我一直收到403错误.请注意,我希望我的信任商店信任任何东西(我知道它不安全,但此时我并不担心).

这是我的代码:

KeyStore keystore = getKeyStore();//Implemented somewhere else and working ok
    String password = "changeme";

    SSLContext context = SSLContext.getInstance("SSL");
    KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmfactory.init(keystore,password.toCharArray());
    context.init(kmfactory.getKeyManagers(),new TrustManager[] { new X509TrustManager() {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] arg0,String arg1)
                throws CertificateException {
        }
        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] arg0,String arg1)
                throws CertificateException {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    } },new SecureRandom());
    SSLSocketFactory sf = new SSLSocketFactory(context);

    Scheme httpsScheme = new Scheme("https",443,sf);
    SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(httpsScheme);
    ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry);
    HttpClient client = new DefaultHttpClient(cm);

    HttpGet get = new HttpGet("https://theurl.com");
    HttpResponse response = client.execute(get);
    System.out.println(EntityUtils.toString(response.getEntity(),"UTF-8"));

最后一个语句打印出403错误.我在这里错过了什么?

解决方法

想出来我自己的. 403错误是因为Java SSL没有选择我的客户端证书.

我调试了SSL握手,发现服务器要求提供由权限列表颁发的客户端证书,而我的客户端证书的颁发者不在该列表中.因此,Java SSL无法在我的密钥库中找到合适的证书.看起来Web浏览器和Java实现SSL的方式略有不同,因为我的浏览器实际上会询问我使用哪个证书,无论服务器证书在客户端证书的颁发者方面要求什么.

在这种情况下,服务器证书是责任.它是自签名的,并且它通知的发布者列表是不完整的.这与Java SSL实现并不完美.但服务器不是我的,除了抱怨巴西政府(他们的服务器)之外,我无能为力.没有进一步的到期,这是我的工作:

首先,我使用了一个信任任何东西的TrustManager(就像我在我的问题中所做的那样):

public class MyTrustManager implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] chain,String authType) throws CertificateException {
    }
    public void checkServerTrusted(X509Certificate[] chain,String authType) throws CertificateException {
    }
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}

然后我实现了一个密钥管理器,它始终使用我想要的PKCS12(.pfx)证书密钥:

public class MyKeyManager extends X509ExtendedKeyManager {

KeyStore keystore = null;
String password = null;
public MyKeyManager(KeyStore keystore,String password) {
        this.keystore = keystore;
        this.password = password;
}

@Override
public String chooseClientAlias(String[] arg0,Principal[] arg1,Socket arg2) {
    return "";//can't be null
}

@Override
public String chooseServerAlias(String arg0,Socket arg2) {
    return null;
}

@Override
public X509Certificate[] getCertificateChain(String arg0) {
    try {
        X509Certificate[] result = new X509Certificate[keystore.getCertificateChain(keystore.aliases().nextElement()).length];
        for (int i=0; i < result.length; i++){
            result[i] = (X509Certificate) keystore.getCertificateChain(keystore.aliases().nextElement())[i];
        }
        return result ;
    } catch (Exception e) {
    }
    return null;
}

@Override
public String[] getClientAliases(String arg0,Principal[] arg1) {
    try {
        return new String[] { keystore.aliases().nextElement() };
    } catch (Exception e) {
        return null;
    }
}

@Override
public PrivateKey getPrivateKey(String arg0) {
    try {
        return ((KeyStore.PrivateKeyEntry) keystore.getEntry(keystore.aliases().nextElement(),new KeyStore.PasswordProtection(password.toCharArray()))).getPrivateKey();
    } catch (Exception e) {
    }
    return null;
}

@Override
public String[] getServerAliases(String arg0,Principal[] arg1) {
    return null;
}

}

如果我的pfx还包含其颁发者证书,这将有效.但它没有(耶!).因此,当我使用上面的密钥管理器时,我收到了SSL握手错误(对等体未经过身份验证).如果客户端发送服务器信任的证书链,则服务器仅对客户端进行身份验证.由于我的证书(由巴西机构颁发)不包含其颁发者,因此其证书链仅包含其自身.服务器不喜欢这样,并拒绝对客户端进行身份验证.解决方法是手动创建证书链:

...
@Override
    //The order matters,your certificate should be the first one in the chain,its issuer the second,its issuer's issuer the third and so on.
public X509Certificate[] getCertificateChain(String arg0) {
            X509Certificate[] result = new X509Certificate[2];
            //The certificate chain contains only one entry in my case
            result[0] = (X509Certificate) keystore.getCertificateChain(keystore.aliases().nextElement())[0];
            //Implement getMyCertificateIssuer() according to your needs. In my case,I read it from a JKS keystore from my database
            result[1] = getMyCertificateIssuer();
            return result;
}
...

在那之后,只需要使用我的自定义密钥和信任管理器:

InputStream keystoreContents = null;//Read it from a file,a byte array or whatever floats your boat
            KeyStore keystore = KeyStore.getInstance("PKCS12");
            keystore.load(keystoreContetns,"changeme".toCharArray());
            SSLContext context = SSLContext.getInstance("TLSv1");
            context.init(new KeyManager[] { new MyKeyManager(keystore,"changeme") },new TrustManager[] { new MyTrustManager() },new SecureRandom());
            SSLSocketFactory sf = new SSLSocketFactory(context);
            Scheme httpsScheme = new Scheme("https",sf);
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(httpsScheme);
            ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry);
            HttpClient client = new DefaultHttpClient(cm);
            HttpPost post = new HttpPost("https://www.someserver.com");

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读