参考内容
官方参考文档:Java Cas Client
Cas Server部署参考 CAS Server部署,基于版本6.6.4
修改配置
添加依赖
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.6.4</version>
</dependency>
<dependency>
<groupId>org.apereo.cas.client</groupId>
<artifactId>cas-client-support-springboot</artifactId>
<version>3.6.4</version>
</dependency>
修改项目配置
cas.server-url-prefix=https://cashost.com/cas
cas.server-login-url=https://cashost.com/cas/login
cas.client-host-url=https://casclient.com
cas.validation-type=CAS3
增加注解@EnableCasClient
@SpringBootApplication
@Controller
@EnableCasClient
public class MyApplication {
}
如果CAS Server配置了SSL,需在客户端jdk系统变量中加入如下配置
System.setProperty("javax.net.ssl.trustStorePassword", "aaa");
System.setProperty("javax.net.ssl.trustStore", "/abc/abc/caskeystore");
#trustStore指定的密钥库需与CAS Server使用的一致
重启即可
对于前后端分离的项目可以通过重写以下类并配合前端实现
CasClientConfiguration
/**
* Licensed to Apereo under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Apereo licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.client.boot.configuration;
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.authentication.Saml11AuthenticationFilter;
import org.jasig.cas.client.configuration.ConfigurationKeys;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.AssertionThreadLocalFilter;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.jasig.cas.client.validation.Saml11TicketValidationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.servlet.Filter;
import java.io.File;
import java.io.FileWriter;
import java.util.Collection;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Configuration class providing default CAS client infrastructure filters.
* This configuration facility is typically imported into Spring's Application Context via
* {@link EnableCasClient} meta annotation.
*
* @author Dmitriy Kopylenko
* @since 3.6.0
*/
@Configuration
@EnableConfigurationProperties(CasClientConfigurationProperties.class)
public class CasClientConfiguration {
@Autowired
CasClientConfigurationProperties configProps;
private CasClientConfigurer casClientConfigurer;
private static Map<String, String> constructInitParams(final String casUrlParamName, final String casUrlParamVal, final String clientHostUrlVal) {
final Map<String, String> initParams = new HashMap<>(2);
initParams.put(casUrlParamName, casUrlParamVal);
initParams.put("serverName", clientHostUrlVal);
return initParams;
}
private static void initFilter(final FilterRegistrationBean filterRegistrationBean,
final Filter targetFilter,
final int filterOrder,
final Map<String, String> initParams,
final List<String> urlPatterns) {
filterRegistrationBean.setFilter(targetFilter);
filterRegistrationBean.setOrder(filterOrder);
filterRegistrationBean.setInitParameters(initParams);
if (!urlPatterns.isEmpty()) {
filterRegistrationBean.setUrlPatterns(urlPatterns);
}
}
@Bean
@ConditionalOnProperty(prefix = "cas", name = "skipTicketValidation", havingValue = "false", matchIfMissing = true)
public FilterRegistrationBean casValidationFilter() {
final FilterRegistrationBean validationFilter = new FilterRegistrationBean();
final Filter targetCasValidationFilter;
switch (this.configProps.getValidationType()) {
case CAS:
targetCasValidationFilter = new Cas20ProxyReceivingTicketValidationFilter();
break;
case SAML:
targetCasValidationFilter = new Saml11TicketValidationFilter();
break;
case CAS3:
default:
targetCasValidationFilter = new Cas30ProxyReceivingTicketValidationFilter();
break;
}
initFilter(validationFilter,
targetCasValidationFilter,
1,
constructInitParams(ConfigurationKeys.CAS_SERVER_URL_PREFIX.getName(), this.configProps.getServerUrlPrefix(), this.configProps.getClientHostUrl()),
this.configProps.getValidationUrlPatterns());
if (this.configProps.getUseSession() != null) {
validationFilter.getInitParameters().put(ConfigurationKeys.USE_SESSION.getName(), String.valueOf(this.configProps.getUseSession()));
}
if (this.configProps.getRedirectAfterValidation() != null) {
validationFilter.getInitParameters().put(ConfigurationKeys.REDIRECT_AFTER_VALIDATION.getName(),
String.valueOf(this.configProps.getRedirectAfterValidation()));
}
if (this.configProps.getHostnameVerifier() != null) {
validationFilter.getInitParameters().put(ConfigurationKeys.HOSTNAME_VERIFIER.getName(), this.configProps.getHostnameVerifier());
}
if (this.configProps.getSslConfigFile() != null) {
validationFilter.getInitParameters().put(ConfigurationKeys.SSL_CONFIG_FILE.getName(), this.configProps.getSslConfigFile());
}
//Proxy tickets validation
if (this.configProps.getAcceptAnyProxy() != null) {
validationFilter.getInitParameters().put(ConfigurationKeys.ACCEPT_ANY_PROXY.getName(), String.valueOf(this.configProps.getAcceptAnyProxy()));
}
if (!this.configProps.getAllowedProxyChains().isEmpty()) {
validationFilter.getInitParameters().put(ConfigurationKeys.ALLOWED_PROXY_CHAINS.getName(),
StringUtils.collectionToDelimitedString(this.configProps.getAllowedProxyChains(), " "));
}
if (this.configProps.getProxyCallbackUrl() != null) {
validationFilter.getInitParameters().put(ConfigurationKeys.PROXY_CALLBACK_URL.getName(), this.configProps.getProxyCallbackUrl());
}
if (this.configProps.getProxyReceptorUrl() != null) {
validationFilter.getInitParameters().put(ConfigurationKeys.PROXY_RECEPTOR_URL.getName(), this.configProps.getProxyReceptorUrl());
}
if (this.casClientConfigurer != null) {
this.casClientConfigurer.configureValidationFilter(validationFilter);
}
return validationFilter;
}
@Bean
public FilterRegistrationBean casAuthenticationFilter() {
final FilterRegistrationBean authnFilter = new FilterRegistrationBean();
final Filter targetCasAuthnFilter =
this.configProps.getValidationType() == EnableCasClient.ValidationType.CAS
|| configProps.getValidationType() == EnableCasClient.ValidationType.CAS3
? new AuthenticationFilter()
: new Saml11AuthenticationFilter();
initFilter(authnFilter,
targetCasAuthnFilter,
2,
constructInitParams(ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(), this.configProps.getServerLoginUrl(), this.configProps.getClientHostUrl()),
this.configProps.getAuthenticationUrlPatterns());
if (this.configProps.getGateway() != null) {
authnFilter.getInitParameters().put(ConfigurationKeys.GATEWAY.getName(), String.valueOf(this.configProps.getGateway()));
}
if (this.casClientConfigurer != null) {
this.casClientConfigurer.configureAuthenticationFilter(authnFilter);
}
return authnFilter;
}
@Bean
public FilterRegistrationBean casHttpServletRequestWrapperFilter() {
final FilterRegistrationBean reqWrapperFilter = new FilterRegistrationBean();
reqWrapperFilter.setFilter(new HttpServletRequestWrapperFilter());
if (!this.configProps.getRequestWrapperUrlPatterns().isEmpty()) {
reqWrapperFilter.setUrlPatterns(this.configProps.getRequestWrapperUrlPatterns());
}
reqWrapperFilter.setOrder(3);
if (this.casClientConfigurer != null) {
this.casClientConfigurer.configureHttpServletRequestWrapperFilter(reqWrapperFilter);
}
return reqWrapperFilter;
}
@Bean
public FilterRegistrationBean casAssertionThreadLocalFilter() {
final FilterRegistrationBean assertionTLFilter = new FilterRegistrationBean();
assertionTLFilter.setFilter(new AssertionThreadLocalFilter());
if (!this.configProps.getAssertionThreadLocalUrlPatterns().isEmpty()) {
assertionTLFilter.setUrlPatterns(this.configProps.getAssertionThreadLocalUrlPatterns());
}
assertionTLFilter.setOrder(4);
if (this.casClientConfigurer != null) {
this.casClientConfigurer.configureAssertionThreadLocalFilter(assertionTLFilter);
}
return assertionTLFilter;
}
@Autowired(required = false)
void setConfigurers(final Collection<CasClientConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException(configurers.size() + " implementations of " +
"CasClientConfigurer were found when only 1 was expected. " +
"Refactor the configuration such that CasClientConfigurer is " +
"implemented only once or not at all.");
}
this.casClientConfigurer = configurers.iterator().next();
}
@Bean
@ConditionalOnProperty(prefix = "cas", value = "single-logout.enabled", havingValue = "true")
public FilterRegistrationBean casSingleSignOutFilter() {
final FilterRegistrationBean singleSignOutFilter = new FilterRegistrationBean();
singleSignOutFilter.setFilter(new SingleSignOutFilter());
final Map<String, String> initParameters = new HashMap<>(1);
initParameters.put(ConfigurationKeys.CAS_SERVER_URL_PREFIX.getName(), configProps.getServerUrlPrefix());
singleSignOutFilter.setInitParameters(initParameters);
singleSignOutFilter.setOrder(Ordered.HIGHEST_PRECEDENCE);
return singleSignOutFilter;
}
@Bean
@ConditionalOnProperty(prefix = "cas", value = "single-logout.enabled", havingValue = "true")
public ServletListenerRegistrationBean<EventListener> casSingleSignOutListener() {
ServletListenerRegistrationBean<EventListener> singleSignOutListener = new ServletListenerRegistrationBean<>();
singleSignOutListener.setListener(new SingleSignOutHttpSessionListener());
singleSignOutListener.setOrder(Ordered.HIGHEST_PRECEDENCE);
return singleSignOutListener;
}
@Configuration
@EnableConfigurationProperties(CasClientConfigurationProperties.class)
@ConditionalOnClass(CasAuthenticationToken.class)
@ConditionalOnProperty(prefix = "cas", value = "use-session", havingValue = "true", matchIfMissing = true)
public class SpringSecurityAssertionAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "springSecurityAssertionSessionContextFilter")
public FilterRegistrationBean springSecurityAssertionSessionContextFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean();
filter.setFilter(new SpringSecurityAssertionSessionContextFilter(springSecurityCasUserDetailsService()));
filter.setEnabled(!configProps.getAttributeAuthorities().isEmpty());
filter.setOrder(0);
if (casClientConfigurer != null) {
casClientConfigurer.configureHttpServletRequestWrapperFilter(filter);
}
return filter;
}
@Bean
@ConditionalOnMissingBean(name = "springSecurityCasUserDetailsService")
public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> springSecurityCasUserDetailsService() {
return new GrantedAuthorityFromAssertionAttributesUserDetailsService(
configProps.getAttributeAuthorities().toArray(new String[]{}));
}
}
}
AuthenticationFilter
/**
* Licensed to Apereo under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Apereo licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.client.authentication;
import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.configuration.ConfigurationKeys;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Filter implementation to intercept all requests and attempt to authenticate
* the user by redirecting them to CAS (unless the user has a ticket).
* <p>
* This filter allows you to specify the following parameters (at either the context-level or the filter-level):
* <ul>
* <li><code>casServerLoginUrl</code> - the url to log into CAS, i.e. https://cas.rutgers.edu/login</li>
* <li><code>renew</code> - true/false on whether to use renew or not.</li>
* <li><code>gateway</code> - true/false on whether to use gateway or not.</li>
* <li><code>method</code> - the method used by the CAS server to send the user back to the application (redirect or post).</li>
* </ul>
*
* <p>Please see AbstractCasFilter for additional properties.</p>
*
* @author Scott Battaglia
* @author Misagh Moayyed
* @since 3.0
*/
public class AuthenticationFilter extends AbstractCasFilter {
/**
* The URL to the CAS Server login.
*/
private String casServerLoginUrl;
/**
* Whether to send the renew request or not.
*/
private boolean renew = false;
/**
* Whether to send the gateway request or not.
*/
private boolean gateway = false;
/**
* The method used by the CAS server to send the user back to the application.
*/
private String method;
private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();
private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null;
private static final Map<String, Class<? extends UrlPatternMatcherStrategy>> PATTERN_MATCHER_TYPES =
new HashMap<String, Class<? extends UrlPatternMatcherStrategy>>();
static {
PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class);
PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class);
PATTERN_MATCHER_TYPES.put("FULL_REGEX", EntireRegionRegexUrlPatternMatcherStrategy.class);
PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class);
}
public AuthenticationFilter() {
this(Protocol.CAS2);
}
protected AuthenticationFilter(final Protocol protocol) {
super(protocol);
}
@Override
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
if (!isIgnoreInitConfiguration()) {
super.initInternal(filterConfig);
final String loginUrl = getString(ConfigurationKeys.CAS_SERVER_LOGIN_URL);
if (loginUrl != null) {
setCasServerLoginUrl(loginUrl);
} else {
setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX));
}
setRenew(getBoolean(ConfigurationKeys.RENEW));
setGateway(getBoolean(ConfigurationKeys.GATEWAY));
setMethod(getString(ConfigurationKeys.METHOD));
final String ignorePattern = getString(ConfigurationKeys.IGNORE_PATTERN);
final String ignoreUrlPatternType = getString(ConfigurationKeys.IGNORE_URL_PATTERN_TYPE);
if (ignorePattern != null) {
final Class<? extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);
if (ignoreUrlMatcherClass != null) {
this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName());
} else {
try {
logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);
this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType);
} catch (final IllegalArgumentException e) {
logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, e);
}
}
if (this.ignoreUrlPatternMatcherStrategyClass != null) {
this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);
}
}
final Class<? extends GatewayResolver> gatewayStorageClass = getClass(ConfigurationKeys.GATEWAY_STORAGE_CLASS);
if (gatewayStorageClass != null) {
setGatewayStorage(ReflectUtils.newInstance(gatewayStorageClass));
}
final Class<? extends AuthenticationRedirectStrategy> authenticationRedirectStrategyClass = getClass(ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS);
if (authenticationRedirectStrategyClass != null) {
this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass);
}
}
}
@Override
public void init() {
super.init();
final String message = String.format(
"one of %s and %s must not be null.",
ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(),
ConfigurationKeys.CAS_SERVER_URL_PREFIX.getName());
CommonUtils.assertNotNull(this.casServerLoginUrl, message);
}
@Override
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
if (isRequestUrlExcluded(request)) {
logger.debug("Request is ignored.");
filterChain.doFilter(request, response);
return;
}
final HttpSession session = request.getSession(false);
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
if (assertion != null) {
filterChain.doFilter(request, response);
return;
}
final String serviceUrl = constructServiceUrl(request, response);
final String ticket = retrieveTicketFromRequest(request);
final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
filterChain.doFilter(request, response);
return;
}
final String modifiedServiceUrl;
logger.debug("no ticket and no assertion found");
if (this.gateway) {
logger.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
logger.debug("Constructed service url: {}", modifiedServiceUrl);
final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway, this.method);
logger.debug("redirecting to \"{}\"", urlToRedirectTo);
this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
}
public final void setRenew(final boolean renew) {
this.renew = renew;
}
public final void setGateway(final boolean gateway) {
this.gateway = gateway;
}
public void setMethod(final String method) {
this.method = method;
}
public final void setCasServerUrlPrefix(final String casServerUrlPrefix) {
setCasServerLoginUrl(CommonUtils.addTrailingSlash(casServerUrlPrefix) + "login");
}
public final void setCasServerLoginUrl(final String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public final void setGatewayStorage(final GatewayResolver gatewayStorage) {
this.gatewayStorage = gatewayStorage;
}
private boolean isRequestUrlExcluded(final HttpServletRequest request) {
if (this.ignoreUrlPatternMatcherStrategyClass == null) {
return false;
}
final StringBuffer urlBuffer = request.getRequestURL();
if (request.getQueryString() != null) {
urlBuffer.append("?").append(request.getQueryString());
}
final String requestUri = urlBuffer.toString();
return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);
}
public final void setIgnoreUrlPatternMatcherStrategyClass(
final UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass) {
this.ignoreUrlPatternMatcherStrategyClass = ignoreUrlPatternMatcherStrategyClass;
}
}