之前做的项目要迭代多租户功能,不同租户对应同一个数据库的多个schema,进行逻辑上的数据隔离。每个租户要求独立域名,但是前端服务和后端服务仍然只有一份(部署是集群部署)。本来只需要在dns服务器上配置一下域名解析就可以了,但是要集成单点登录cas和安全框架(security), cas 中原生的类是不支持多个serve-name(服务域名)的,需要修改一下cas中的一些组件,所以总结一下。
看一下关键的配置文件:SecurityConfiguration
package com.xxx.config;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import com.xxx.handler.multidomain.MDCasAuthenticationEntryPoint;
import com.xxx.handler.multidomain.MDCasServiceTicketValidator;
import com.xxx.handler.multidomain.MDSimpleUrlLogoutSuccessHandler;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
/**
* cas与security整合,需要配置的对象
* <p>
* 在cas与security整合中,首先需要做的是将应用的登录认证入口改为使用CasAuthenticationEntryPoint。
* 所以首先我们需要配置一个CasAuthenticationEntryPoint对应的bean,
* 然后指定需要进行登录认证时使用该AuthenticationEntryPoint。
* 配置CasAuthenticationEntryPoint时需要指定一个ServiceProperties,
* 该对象主要用来描述service (Cas概念) 相关的属性,主要是指定在Cas Server认证成功后将要跳转的地址。
* <p>
* CasAuthenticationFilter认证过滤器,负责认证跳转和票据验证
*/
@Configuration
@EnableConfigurationProperties(value = {com.xxx.config.CasServerProperties.class})
public class SecurityConfiguration {
@Resource
private com.xxx.config.CasServerProperties casServerProperties;
@Resource
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> userDetailsService;
@Bean
public AuthenticationManager authenticationManager(CasAuthenticationProvider provider) {
List<AuthenticationProvider> providers = new ArrayList<>();
providers.add(provider);
ProviderManager providerManager = new ProviderManager(providers);
return providerManager;
}
/**
* 我们自己应用的配置信息,该对象主要用于构建CasAuthenticationEntryPoint。
*
* @return
*/
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
//设置默认的cas登陆后回跳地址
serviceProperties.setService(casServerProperties.getServerName() + "/login");
//设置我们应用是否敏感
serviceProperties.setSendRenew(false);
//设置是否对未拥有ticket的访问均需要验证
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
/**
* CAS认证过滤器,主要实现票据认证和认证成功后的跳转。
*
* @param auth
* @param serviceProperties
* @return
*/
@Bean
public CasAuthenticationFilter casAuthenticationFilter(AuthenticationManager auth, ServiceProperties serviceProperties) {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
//给过滤器设置我们应用的基本配置
casAuthenticationFilter.setServiceProperties(serviceProperties);
//给过滤器设置认证管理器
casAuthenticationFilter.setAuthenticationManager(auth);
//设置过滤器到cas server认证的地址
casAuthenticationFilter.setFilterProcessesUrl(casServerProperties.getCasServerLoginUrl());
//设置是否继续执行其他过滤器,在完成认证前
casAuthenticationFilter.setContinueChainBeforeSuccessfulAuthentication(false);
//设置认证成功后的处理handler, 目前使用默认的SavedRequestAwareAuthenticationSuccessHandler
// casAuthenticationFilter.setAuthenticationSuccessHandler(new AddressBarUrlAuthenticationSuccessHandler());
// casAuthenticationFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/demo/admin"));
return casAuthenticationFilter;
}
/**
* 认证的入口,即跳转至服务端的cas地址
* security框架整合cas认证的入口,也就是security不再走自己的认证入口,而是cas的,该对象就是cas的认证入口
*
* @param serviceProperties
* @return
*/
@Bean
public MDCasAuthenticationEntryPoint mCasAuthenticationEntryPoint(ServiceProperties serviceProperties) {
MDCasAuthenticationEntryPoint mdCasAuthenticationEntryPoint = new MDCasAuthenticationEntryPoint();
//security框架整合cas认证的入口,也就是security不再走自己的认证入口,而是cas的,该对象就是cas的认证入口
mdCasAuthenticationEntryPoint.setServiceProperties(serviceProperties);
mdCasAuthenticationEntryPoint.setLoginUrl(casServerProperties.getCasServerLoginUrl());
return mdCasAuthenticationEntryPoint;
}
/**
* 配置TicketValidator在登录认证成功后验证ticket
* 该对象就是一个ticket校验器
*
* @return
*/
@Bean
public MDCasServiceTicketValidator cas20ServiceTicketValidator() {
//需要设置cas server的前缀,也就是根路径
return new MDCasServiceTicketValidator(casServerProperties.getCasServerUrlPrefix());
}
/**
* 该对象为cas校验对象,TicketValidator、AuthenticationUserDetailService属性必须设置;
* serviceProperties属性主要应用于ticketValidator用于去cas服务端校验ticket
*
* @param userDetailsService
* @param serviceProperties
* @param ticketValidator
* @return
*/
@Bean("casProvider")
public CasAuthenticationProvider casAuthenticationProvider(AuthenticationUserDetailsService<CasAssertionAuthenticationToken>
userDetailsService,
ServiceProperties serviceProperties,
MDCasServiceTicketValidator ticketValidator) {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setKey("casProvider");
provider.setServiceProperties(serviceProperties);
provider.setTicketValidator(ticketValidator);
provider.setAuthenticationUserDetailsService(userDetailsService);
return provider;
}
@Bean
public LogoutFilter logoutFilter(MDSimpleUrlLogoutSuccessHandler mdSimpleUrlLogoutSuccessHandler) {
LogoutFilter logoutFilter = new LogoutFilter(mdSimpleUrlLogoutSuccessHandler, new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl("/checkSession");
return logoutFilter;
}
@Bean
public MDSimpleUrlLogoutSuccessHandler mdSimpleUrlLogoutSuccessHandler() {
String logoutRedirectPath = casServerProperties.getCasServerLogoutUrl();
MDSimpleUrlLogoutSuccessHandler mdSimpleUrlLogoutSuccessHandler = new MDSimpleUrlLogoutSuccessHandler();
mdSimpleUrlLogoutSuccessHandler.setDefaultTargetUrl(logoutRedirectPath);
return mdSimpleUrlLogoutSuccessHandler;
}
}
MDCasAuthenticationEntryPoint我们自己定义的入口类,实现接口AuthenticationEntryPoint,主要是copy了CasAuthenticationEntryPoint的内容,修改了createServiceUrl的逻辑,这个方法主要是创建服务地址(域名),一个项目只能有一个,因此改造它,根据请求的域名动态进行生成。
package com.xxx.handler.multidomain;
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xxx.config.CasServerProperties;
import com.xxx.util.ProgramVariable;
import org.jasig.cas.client.util.CommonUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.util.Assert;
/**
* author:lgq
* date:2022-1-1
*/
public class MDCasAuthenticationEntryPoint implements AuthenticationEntryPoint,
InitializingBean {
@Resource
private ProgramVariable programVariable;
@Resource
private CasServerProperties casServerProperties;
private ServiceProperties serviceProperties;
private String loginUrl;
/**
* Determines whether the Service URL should include the session id for the specific
* user. As of CAS 3.0.5, the session id will automatically be stripped. However,
* older versions of CAS (i.e. CAS 2), do not automatically strip the session
* identifier (this is a bug on the part of the older server implementations), so an
* option to disable the session encoding is provided for backwards compatibility.
*
* By default, encoding is enabled.
*/
private boolean encodeServiceUrlWithSessionId = true;
// ~ Methods
// ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasLength(this.loginUrl, "loginUrl must be specified");
Assert.notNull(this.serviceProperties, "serviceProperties must be specified");
Assert.notNull(this.serviceProperties.getService(),
"serviceProperties.getService() cannot be null.");
}
public final void commence(final HttpServletRequest servletRequest,
final HttpServletResponse response,
final AuthenticationException authenticationException) throws IOException,
ServletException {
final String urlEncodedService = createServiceUrl(servletRequest, response);
final String redirectUrl = createRedirectUrl(urlEncodedService);
preCommence(servletRequest, response);
response.sendRedirect(redirectUrl);
}
/**
* Constructs a new Service Url. The default implementation relies on the CAS client
* to do the bulk of the work.
* @param request the HttpServletRequest
* @param response the HttpServlet Response
* @return the constructed service url. CANNOT be NULL.
* 改造该方法,根据请求url动态获取服务地址
*/
protected String createServiceUrl(final HttpServletRequest request,
final HttpServletResponse response) {
//自定义方法
String service = getService(request);
this.serviceProperties.setService(service);
return CommonUtils.constructServiceUrl(null, response,
service, null,
this.serviceProperties.getArtifactParameter(),
this.encodeServiceUrlWithSessionId);
}
/**
* Constructs the Url for Redirection to the CAS server. Default implementation relies
* on the CAS client to do the bulk of the work.
*
* @param serviceUrl the service url that should be included.
* @return the redirect url. CANNOT be NULL.
*/
protected String createRedirectUrl(final String serviceUrl) {
return CommonUtils.constructRedirectUrl(this.loginUrl,
this.serviceProperties.getServiceParameter(), serviceUrl,
this.serviceProperties.isSendRenew(), false);
}
/**
* Template method for you to do your own pre-processing before the redirect occurs.
*
* @param request the HttpServletRequest
* @param response the HttpServletResponse
*/
protected void preCommence(final HttpServletRequest request,
final HttpServletResponse response) {
}
/**
* The enterprise-wide CAS login URL. Usually something like
* <code>https://www.mycompany.com/cas/login</code>.
*
* @return the enterprise-wide CAS login URL
*/
public final String getLoginUrl() {
return this.loginUrl;
}
public final ServiceProperties getServiceProperties() {
return this.serviceProperties;
}
public final void setLoginUrl(final String loginUrl) {
this.loginUrl = loginUrl;
}
public final void setServiceProperties(final ServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
/**
* Sets whether to encode the service url with the session id or not.
*
* @param encodeServiceUrlWithSessionId whether to encode the service url with the
* session id or not.
*/
public final void setEncodeServiceUrlWithSessionId(
final boolean encodeServiceUrlWithSessionId) {
this.encodeServiceUrlWithSessionId = encodeServiceUrlWithSessionId;
}
/**
* Sets whether to encode the service url with the session id or not.
* @return whether to encode the service url with the session id or not.
*
*/
protected boolean getEncodeServiceUrlWithSessionId() {
return this.encodeServiceUrlWithSessionId;
}
/**
* 根据请求url设置服务地址,即域名
*
*/
private String getService(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if (url.contains("www.alibabagroup.com")) {
return programVariable.getAliServiceName() + "/login";
} else if (url.contains("www.tencent.com") ) {
return programVariable.getTencentServerName() + "/login";
} else {
return casServerProperties.getServerName() + "/login";
}
}
}
输入账号密码后,客户端获得ticket票据,还需要到cas服务器进行验证,这里需要访问应用服务地址,也需要和一开始输入的url是同一个域名,否则会报错。我定义的类MDCasServiceTicketValidator实现了TicketValidator接口,主要是汇总了Cas20ServiceTicketValidator及其父类AbstractCasProtocolUrlBasedTicketValidator中的内容。重写validate方法,目的是动态修改validationUrl,修改的逻辑和上面描述的一样。
package com.xxx.handler.multidomain;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import com.xxx.config.CasServerProperties;
import com.xxx.util.ProgramVariable;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.proxy.Cas20ProxyRetriever;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.proxy.ProxyRetriever;
import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
import org.jasig.cas.client.ssl.HttpsURLConnectionFactory;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* author:lgq
* date:2022-1-1
*/
public class MDCasServiceTicketValidator implements TicketValidator {
@Resource
private ProgramVariable programVariable;
@Resource
private CasServerProperties casServerProperties;
protected final Logger logger = LoggerFactory.getLogger(getClass());
/** The CAS 2.0 protocol proxy callback url. */
private String proxyCallbackUrl;
/** The storage location of the proxy granting tickets. */
private ProxyGrantingTicketStorage proxyGrantingTicketStorage;
/** Implementation of the proxy retriever. */
private ProxyRetriever proxyRetriever;
/**
* Prefix for the CAS server. Should be everything up to the url endpoint, including the /.
*
* i.e. https://cas.rutgers.edu/
*/
private final String casServerUrlPrefix;
/**
* URLConnection factory instance to use when making validation requests to the CAS server.
* Defaults to {@link HttpsURLConnectionFactory}
*/
private HttpURLConnectionFactory urlConnectionFactory = new HttpsURLConnectionFactory();
/**
* Whether the request include a renew or not.
*/
private boolean renew;
/**
* A map containing custom parameters to pass to the validation url.
*/
private Map<String, String> customParameters;
private String encoding;
/**
* Constructs an instance of the CAS 2.0 Service Ticket Validator with the supplied
* CAS server url prefix.
*
* @param casServerUrlPrefix the CAS Server URL prefix.
*/
public MDCasServiceTicketValidator(final String casServerUrlPrefix) {
CommonUtils.assertNotNull(casServerUrlPrefix, "casServerUrlPrefix cannot be null.");
this.casServerUrlPrefix = CommonUtils.addTrailingSlash(casServerUrlPrefix);
this.proxyRetriever = new Cas20ProxyRetriever(casServerUrlPrefix, getEncoding(), getURLConnectionFactory());
}
/**
* Adds the pgtUrl to the list of parameters to pass to the CAS server.
*
* @param urlParameters the Map containing the existing parameters to send to the server.
*/
protected final void populateUrlAttributeMap(final Map<String, String> urlParameters) {
urlParameters.put("pgtUrl", this.proxyCallbackUrl);
}
protected String getUrlSuffix() {
return "serviceValidate";
}
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
final String error = parseAuthenticationFailureFromResponse(response);
if (CommonUtils.isNotBlank(error)) {
throw new TicketValidationException(error);
}
final String principal = parsePrincipalFromResponse(response);
final String proxyGrantingTicketIou = parseProxyGrantingTicketFromResponse(response);
final String proxyGrantingTicket;
if (CommonUtils.isBlank(proxyGrantingTicketIou) || this.proxyGrantingTicketStorage == null) {
proxyGrantingTicket = null;
} else {
proxyGrantingTicket = this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou);
}
if (CommonUtils.isEmpty(principal)) {
throw new TicketValidationException("No principal was found in the response from the CAS server.");
}
final Assertion assertion;
final Map<String, Object> attributes = extractCustomAttributes(response);
if (CommonUtils.isNotBlank(proxyGrantingTicket)) {
final AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes,
proxyGrantingTicket, this.proxyRetriever);
assertion = new AssertionImpl(attributePrincipal);
} else {
assertion = new AssertionImpl(new AttributePrincipalImpl(principal, attributes));
}
customParseResponse(response, assertion);
return assertion;
}
protected String parseProxyGrantingTicketFromResponse(final String response) {
return XmlUtils.getTextForElement(response, "proxyGrantingTicket");
}
protected String parsePrincipalFromResponse(final String response) {
return XmlUtils.getTextForElement(response, "user");
}
protected String parseAuthenticationFailureFromResponse(final String response) {
return XmlUtils.getTextForElement(response, "authenticationFailure");
}
/**
* Default attribute parsing of attributes that look like the following:
* <cas:attributes>
* <cas:attribute1>value</cas:attribute1>
* <cas:attribute2>value</cas:attribute2>
* </cas:attributes>
* <p>
*
* Attributes look like following also parsed correctly:
* <cas:attributes><cas:attribute1>value</cas:attribute1><cas:attribute2>value<
* /cas:attribute2></cas:attributes>
* <p>
*
* This code is here merely for sample/demonstration purposes for those wishing to modify the CAS2 protocol. You'll
* probably want a more robust implementation or to use SAML 1.1
*
* @param xml the XML to parse.
* @return the map of attributes.
*/
protected Map<String, Object> extractCustomAttributes(final String xml) {
final SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(false);
try {
final SAXParser saxParser = spf.newSAXParser();
final XMLReader xmlReader = saxParser.getXMLReader();
final MDCasServiceTicketValidator.CustomAttributeHandler handler = new MDCasServiceTicketValidator.CustomAttributeHandler();
xmlReader.setContentHandler(handler);
xmlReader.parse(new InputSource(new StringReader(xml)));
return handler.getAttributes();
} catch (final Exception e) {
logger.error(e.getMessage(), e);
return Collections.emptyMap();
}
}
/**
* Template method if additional custom parsing (such as Proxying) needs to be done.
*
* @param response the original response from the CAS server.
* @param assertion the partially constructed assertion.
* @throws TicketValidationException if there is a problem constructing the Assertion.
*/
protected void customParseResponse(final String response, final Assertion assertion)
throws TicketValidationException {
// nothing to do
}
public final void setProxyCallbackUrl(final String proxyCallbackUrl) {
this.proxyCallbackUrl = proxyCallbackUrl;
}
public final void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
}
public final void setProxyRetriever(final ProxyRetriever proxyRetriever) {
this.proxyRetriever = proxyRetriever;
}
protected final String getProxyCallbackUrl() {
return this.proxyCallbackUrl;
}
protected final ProxyGrantingTicketStorage getProxyGrantingTicketStorage() {
return this.proxyGrantingTicketStorage;
}
protected final ProxyRetriever getProxyRetriever() {
return this.proxyRetriever;
}
/**
*
*/
private class CustomAttributeHandler extends DefaultHandler {
private Map<String, Object> attributes;
private boolean foundAttributes;
private String currentAttribute;
private StringBuilder value;
@Override
public void startDocument() throws SAXException {
this.attributes = new HashMap<String, Object>();
}
@Override
public void startElement(final String namespaceURI, final String localName, final String qName,
final Attributes attributes) throws SAXException {
if ("attributes".equals(localName)) {
this.foundAttributes = true;
} else if (this.foundAttributes) {
this.value = new StringBuilder();
this.currentAttribute = localName;
}
}
@Override
public void characters(final char[] chars, final int start, final int length) throws SAXException {
if (this.currentAttribute != null) {
value.append(chars, start, length);
}
}
@Override
public void endElement(final String namespaceURI, final String localName, final String qName)
throws SAXException {
if ("attributes".equals(localName)) {
this.foundAttributes = false;
this.currentAttribute = null;
} else if (this.foundAttributes) {
final Object o = this.attributes.get(this.currentAttribute);
if (o == null) {
this.attributes.put(this.currentAttribute, this.value.toString());
} else {
final List<Object> items;
if (o instanceof List) {
items = (List<Object>) o;
} else {
items = new LinkedList<Object>();
items.add(o);
this.attributes.put(this.currentAttribute, items);
}
items.add(this.value.toString());
}
}
}
public Map<String, Object> getAttributes() {
return this.attributes;
}
}
protected final String getEncoding() {
return this.encoding;
}
protected HttpURLConnectionFactory getURLConnectionFactory() {
return this.urlConnectionFactory;
}
/**
* Constructs the URL to send the validation request to.
*
* @param ticket the ticket to be validated.
* @param serviceUrl the service identifier.
* @return the fully constructed URL.
*/
protected final String constructValidationUrl(final String ticket, final String serviceUrl) {
final Map<String, String> urlParameters = new HashMap<String, String>();
logger.debug("Placing URL parameters in map.");
urlParameters.put("ticket", ticket);
urlParameters.put("service", serviceUrl);
if (this.renew) {
urlParameters.put("renew", "true");
}
logger.debug("Calling template URL attribute map.");
populateUrlAttributeMap(urlParameters);
logger.debug("Loading custom parameters from configuration.");
if (this.customParameters != null) {
urlParameters.putAll(this.customParameters);
}
final String suffix = getUrlSuffix();
final StringBuilder buffer = new StringBuilder(urlParameters.size() * 10 + this.casServerUrlPrefix.length()
+ suffix.length() + 1);
int i = 0;
buffer.append(this.casServerUrlPrefix);
buffer.append(suffix);
for (Map.Entry<String, String> entry : urlParameters.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
if (value != null) {
buffer.append(i++ == 0 ? "?" : "&");
buffer.append(key);
buffer.append("=");
final String encodedValue = encodeUrl(value);
buffer.append(encodedValue);
}
}
return buffer.toString();
}
/**
* Encodes a URL using the URLEncoder format.
*
* @param url the url to encode.
* @return the encoded url, or the original url if "UTF-8" character encoding could not be found.
*/
protected final String encodeUrl(final String url) {
if (url == null) {
return null;
}
try {
return URLEncoder.encode(url, "UTF-8");
} catch (final UnsupportedEncodingException e) {
return url;
}
}
/**
* Retrieves the response from the server by opening a connection and merely reading the response.
*/
protected final String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
return CommonUtils.getResponseFromServer(validationUrl, getURLConnectionFactory(), getEncoding());
}
/**
* Contacts the CAS Server to retrieve the response for the ticket validation.
*
* @param ticket the ticket to validate.
* @return the response from the CAS server.
* 改造方法,动态获取validationUrl
*/
public final Assertion validate(final String ticket, final String service) throws TicketValidationException {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String serviceUrl;
if (url.contains("www.alibabagroup.com")) {
serviceUrl = programVariable.getAliServiceName() + "/login";
} else if (url.contains("www.tencent.com") ) {
serviceUrl = programVariable.getTencentServerName() + "/login";
} else {
serviceUrl = casServerProperties.getServerName() + "/login";
}
final String validationUrl = constructValidationUrl(ticket, serviceUrl);
logger.debug("Constructing validation url: {}", validationUrl);
try {
logger.debug("Retrieving response from server.");
final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);
if (serverResponse == null) {
throw new TicketValidationException("The CAS server returned no response.");
}
logger.debug("Server response: {}", serverResponse);
return parseResponseFromServer(serverResponse);
} catch (final MalformedURLException e) {
throw new TicketValidationException(e);
}
}
public final void setRenew(final boolean renew) {
this.renew = renew;
}
public final void setCustomParameters(final Map<String, String> customParameters) {
this.customParameters = customParameters;
}
public final void setEncoding(final String encoding) {
this.encoding = encoding;
}
protected final boolean isRenew() {
return this.renew;
}
protected final String getCasServerUrlPrefix() {
return this.casServerUrlPrefix;
}
protected final Map<String, String> getCustomParameters() {
return this.customParameters;
}
public void setURLConnectionFactory(final HttpURLConnectionFactory urlConnectionFactory) {
this.urlConnectionFactory = urlConnectionFactory;
}
}
最后是登出的处理,登出完成后再登录还需要访问之前的域名,因此需要动态获取登出后重定向的地址。自定义类MDSimpleUrlLogoutSuccessHandler模仿SimpleUrlLogoutSuccessHandler类(默认类),只有一个方法,即重写onLogoutSuccess方法。动态获取地址的逻辑和上面一样。
package com.xxx.handler.multidomain;
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xxx.util.ProgramVariable;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
/**
* author:lgq
* date:2022-1-1
*/
public class MDSimpleUrlLogoutSuccessHandler extends
AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {
@Resource
private ProgramVariable programVariable;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
String targetUrl;
String url = request.getRequestURL().toString();
if (url.contains("www.alibabagroup.com")) {
targetUrl = programVariable.getAliServiceName() + "/login";
} else if (url.contains("www.tencent.com") ) {
targetUrl = programVariable.getTencentServerName() + "/login";
} else {
targetUrl = casServerProperties.getServerName() + "/login";
}
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}