springboot security 集成 cas 问题 No subject alternative names present


前言

场景:
在一次springboot security 集成 cas开发中,代码报错:java.security.cert.CertificateException: No subject alternative names present。
环境:
springboot security 集成 cas,cas 服务是其他厂家开发,使用https 协议,所以服务端无法改动,只能在客户端排查问题。


一、问题

从错误信息得到大概意思就是在调用https请求时候校验证书异常。

说到这个问题,我自己搭建一个 https服务, 使用 jdk 自带 keytool 工具生成证书验证了一下:
要能正确的调用https 请求, 要满足
1.客户端要导入由服务端通过 keystore 导出的证书文件;
2.服务生成证书时候填写的CN 信息(域名) 和客户端访问的url的域名要一致(本地测试可以修改hosts文件配置域名)。

1.实际问题

但是!
第三方厂家 cas 服务端的https 证书就随便搞的,连CN信息都是随便写的不知道啥意思。
而且,虽是https 但还是通过ip 端口方式访问。 *哎, 这明显是应付入网安评嘛 *
让我如何是好!

二、大海捞针

1.星星之火

网上搜了很多。
只能绕过 https 证书校验去访问了。
看到一篇和我同样问题的文章:记一次单点登录https中证书无效的问题
他的方式是修改 CommonUtils 源码,然后重新编译打包替换原来的包,这种方式虽说能解决但是不通用,为啥尼,比如代码需要重新下载依赖,那还是每次都要重新替换,而且其他项目想校验证书用到了这个类,就难办了。

而且。他的答案漏了一行重要的代码,下文会提到。

2.通用方法

对于java 需要修改源码,我们一般会使用继承重写的方式。
但是对应本项目。
在这里插入图片描述
问题就出现在 CommonUtils类中 getResponseFromServer 这个方法。不能在这地方直接修改,否则只能像刚才那个网友搞了。找找到哪里用到这个地方。
往前找
在这里插入图片描述

AbstractCasProtocolUrlBasedTicketValidator.class-retrieveResponseFromServer 一看 final 修饰,没戏,再往前找。

在这里插入图片描述

AbstractUrlBasedTicketValidator.class - validate 该方法被final 修饰,重写不了。再往前

在这里插入图片描述
CasAuthenticationProvider.class - authenticateNow , 这个是 private 私有方法,重写不了。
再往前
在这里插入图片描述
CasAuthenticationProvider.class - authenticate ,哎CasAuthenticationProvider,好熟悉的眼神, 没错就是你了,在配置cas就见过你。再加上这个方法是 public,于是就重写这个方法,从这开刀。

啰嗦一句

建议新手多多学会使用 debuger, 你会发现越来越上瘾
在这里插入图片描述


解决

2.新建三个类

在这里插入图片描述
CasAuthenticationProviderExt

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package cn.com.liyx.learn.lyxsec.ext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.*;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.*;
import org.springframework.util.Assert;

public class CasAuthenticationProviderExt extends CasAuthenticationProvider {
    private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
    private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
    private Cas20ProxyTicketValidatorExt ticketValidator;

    private ServiceProperties serviceProperties;

    public void afterPropertiesSet() {
        Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
    }

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!this.supports(authentication.getClass())) {
            return null;
        } else if (authentication instanceof UsernamePasswordAuthenticationToken && !"_cas_stateful_".equals(authentication.getPrincipal().toString()) && !"_cas_stateless_".equals(authentication.getPrincipal().toString())) {
            return null;
        } else if (authentication instanceof CasAuthenticationToken) {
            if (getKey().hashCode() == ((CasAuthenticationToken) authentication).getKeyHash()) {
                return authentication;
            } else {
                throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProviderExt.incorrectKey", "The presented CasAuthenticationToken does not contain the expected key"));
            }
        } else if (authentication.getCredentials() != null && !"".equals(authentication.getCredentials())) {
            boolean stateless = false;
            if (authentication instanceof UsernamePasswordAuthenticationToken && "_cas_stateless_".equals(authentication.getPrincipal())) {
                stateless = true;
            }

            CasAuthenticationToken result = null;
            if (stateless) {
                result = getStatelessTicketCache().getByTicketId(authentication.getCredentials().toString());
            }

            if (result == null) {
                result = this.authenticateNow(authentication);
                result.setDetails(authentication.getDetails());
            }

            if (stateless) {
                getStatelessTicketCache().putTicketInCache(result);
            }

            return result;
        } else {
            throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProviderExt.noServiceTicket", "Failed to provide a CAS service ticket to validate"));
        }
    }

    private CasAuthenticationToken authenticateNow(Authentication authentication) throws AuthenticationException {
        try {
            Assertion assertion = this.ticketValidator.validate2(authentication.getCredentials().toString(), this.getServiceUrl(authentication));
            UserDetails userDetails = this.loadUserByAssertion(assertion);
            userDetailsChecker.check(userDetails);
            return new CasAuthenticationToken(getKey(), userDetails, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion);
        } catch (TicketValidationException var4) {
            throw new BadCredentialsException(var4.getMessage(), var4);
        }
    }

    private String getServiceUrl(Authentication authentication) {
        String serviceUrl;
        if (authentication.getDetails() instanceof ServiceAuthenticationDetails) {
            serviceUrl = ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl();
        } else {
            if (this.serviceProperties == null) {
                throw new IllegalStateException("serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
            }

            if (this.serviceProperties.getService() == null) {
                throw new IllegalStateException("serviceProperties.getService() cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
            }

            serviceUrl = this.serviceProperties.getService();
        }

        if (logger.isDebugEnabled()) {
            logger.debug("serviceUrl = " + serviceUrl);
        }

        return serviceUrl;
    }

    public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
        this.authoritiesMapper = authoritiesMapper;
    }

    public void setServiceProperties(ServiceProperties serviceProperties) {
        this.serviceProperties = serviceProperties;
    }

    protected Cas20ProxyTicketValidatorExt getTicketValidator() {
        return this.ticketValidator;
    }

    public void setTicketValidator(Cas20ProxyTicketValidatorExt ticketValidator) {
        this.ticketValidator = ticketValidator;
    }
}

Cas20ProxyTicketValidatorExt

package cn.com.liyx.learn.lyxsec.ext;

import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.jasig.cas.client.validation.TicketValidationException;

import java.net.MalformedURLException;
import java.net.URL;

public class Cas20ProxyTicketValidatorExt extends Cas20ProxyTicketValidator {
    public Cas20ProxyTicketValidatorExt(String casServerUrlPrefix) {
        super(casServerUrlPrefix);
    }

    public final Assertion validate2(String ticket, String service) throws TicketValidationException {
        String validationUrl = this.constructValidationUrl(ticket, service);
        this.logger.debug("Constructing validation url: {}", validationUrl);

        try {
            this.logger.debug("Retrieving response from server.");
//            String serverResponse = this.retrieveResponseFromServer(new URL(validationUrl), ticket);
            String serverResponse = CommonUtilsExt.getResponseFromServer(new URL(validationUrl), this.getURLConnectionFactory(), this.getEncoding());
            if (serverResponse == null) {
                throw new TicketValidationException("The CAS server returned no response.");
            } else {
                this.logger.debug("Server response: {}", serverResponse);
                return this.parseResponseFromServer(serverResponse);
            }
        } catch (MalformedURLException var5) {
            throw new TicketValidationException(var5);
        }
    }
}

CommonUtilsExt

package cn.com.liyx.learn.lyxsec.ext;

import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
import org.jasig.cas.client.util.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

public class CommonUtilsExt {
    private static final Logger LOGGER = LoggerFactory.getLogger(CommonUtils.class);

    static {
        disableSslVerification();
    }

    private static void disableSslVerification() {
        try {
            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            }
            };
            // Install the all-trusting trust manager
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            // Create all-trusting host name verifier
            HostnameVerifier allHostsValid = new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            };
            // Install the all-trusting host verifier
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
    }

    public static String getResponseFromServer(URL constructedUrl, HttpURLConnectionFactory factory, String encoding) {

        HttpURLConnection conn = null;
        InputStreamReader in = null;

        try {
//            conn = factory.buildHttpURLConnection(constructedUrl.openConnection());  // 原来的写法。
            conn = (HttpURLConnection) constructedUrl.openConnection();  // 新的写法, (注)本文要这样的写法
            if (CommonUtils.isEmpty(encoding)) {
                in = new InputStreamReader(conn.getInputStream());
            } else {
                in = new InputStreamReader(conn.getInputStream(), encoding);
            }

            StringBuilder builder = new StringBuilder(255);

            int byteRead;
            while ((byteRead = in.read()) != -1) {
                builder.append((char) byteRead);
            }
            String var7 = builder.toString();
            return var7;
        } catch (RuntimeException var13) {
            throw var13;
        } catch (SSLException var14) {
            LOGGER.error("SSL error getting response from host: {} : Error Message: {}", new Object[]{constructedUrl.getHost(), var14.getMessage(), var14});
            throw new RuntimeException(var14);
        } catch (IOException var15) {
            LOGGER.error("Error getting response from host: [{}] with path: [{}] and protocol: [{}] Error Message: {}", new Object[]{constructedUrl.getHost(), constructedUrl.getPath(), constructedUrl.getProtocol(), var15.getMessage(), var15});
            throw new RuntimeException(var15);
        } finally {
            CommonUtils.closeQuietly(in);
            if (conn != null) {
                conn.disconnect();
            }

        }
    }

}

前面说那个网友漏掉重要一行, 就是下图这一行, 确实重要!!!
在这里插入图片描述

配置文件修改

在这里插入图片描述

注意就是方法返回类型 以及 属性类型也需要用新增的类名,不然会莫名报错。

完工。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值