mmp,从11年开始用cas,但是总是在退出的时候掉链子,各种掉线子 从2.x版本开始用现在都4.2.7版本都没解决这个退出掉链子的事情,于是自己看源码解决了此问题。
cas 默认的基于 httpclient http 通知的,通知的时候,服务端给客户端发一个xml 里面有一个ticketid,客户端用ticketid做退出。实记使用过程中,你会碰到各种退不出来的问题,关闭浏览器在重试,又TMD好了,为了解决这个问题,我使用mq做通知,不用httpclient做通知了。
方案:
重写服务端注销session的部分代码。
package org.jasig.cas;
import com.alibaba.fastjson.JSONObject;
import com.codahale.metrics.annotation.Counted;
import com.codahale.metrics.annotation.Metered;
import com.codahale.metrics.annotation.Timed;
import org.jasig.cas.authentication.Authentication;
import org.jasig.cas.authentication.AuthenticationBuilder;
import org.jasig.cas.authentication.AuthenticationContext;
import org.jasig.cas.authentication.AuthenticationException;
import org.jasig.cas.authentication.DefaultAuthenticationBuilder;
import org.jasig.cas.authentication.MixedPrincipalException;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.logout.LogoutManager;
import org.jasig.cas.logout.LogoutRequest;
import org.jasig.cas.services.RegisteredService;
import org.jasig.cas.services.RegisteredServiceAttributeReleasePolicy;
import org.jasig.cas.services.ServiceContext;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.services.UnauthorizedProxyingException;
import org.jasig.cas.services.UnauthorizedServiceForPrincipalException;
import org.jasig.cas.services.UnauthorizedSsoServiceException;
import org.jasig.cas.support.events.CasProxyGrantingTicketCreatedEvent;
import org.jasig.cas.support.events.CasProxyTicketGrantedEvent;
import org.jasig.cas.support.events.CasServiceTicketGrantedEvent;
import org.jasig.cas.support.events.CasServiceTicketValidatedEvent;
import org.jasig.cas.support.events.CasTicketGrantingTicketCreatedEvent;
import org.jasig.cas.support.events.CasTicketGrantingTicketDestroyedEvent;
import org.jasig.cas.ticket.AbstractTicketException;
import org.jasig.cas.ticket.InvalidTicketException;
import org.jasig.cas.ticket.ServiceTicket;
import org.jasig.cas.ticket.ServiceTicketFactory;
import org.jasig.cas.ticket.TicketFactory;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.TicketGrantingTicketFactory;
import org.jasig.cas.ticket.UnrecognizableServiceForServiceTicketValidationException;
import org.jasig.cas.ticket.proxy.ProxyGrantingTicket;
import org.jasig.cas.ticket.proxy.ProxyGrantingTicketFactory;
import org.jasig.cas.ticket.proxy.ProxyTicket;
import org.jasig.cas.ticket.proxy.ProxyTicketFactory;
import org.jasig.cas.ticket.registry.TicketRegistry;
import org.jasig.cas.validation.Assertion;
import org.jasig.cas.validation.ImmutableAssertion;
import org.jasig.inspektr.audit.annotation.Audit;
import org.jose4j.json.internal.json_simple.JSONArray;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Concrete implementation of a {@link CentralAuthenticationService}, and also the
* central, organizing component of CAS's internal implementation.
* This class is threadsafe.
*
* @author William G. Thompson, Jr.
* @author Scott Battaglia
* @author Dmitry Kopylenko
* @author Misagh Moayyed
* @since 3.0.0
*/
@Component("centralAuthenticationService")
@Transactional(readOnly = false, transactionManager = "ticketTransactionManager")
public class CentralAuthenticationServiceImpl extends AbstractCentralAuthenticationService {
private static final long serialVersionUID = -8943828074939533986L;
/** 广播交换机名称 */
private final static String EXCHANGE_NAME = "session-destroy-notify";
@Autowired
private RabbitTemplate template;
/**
* Instantiates a new Central authentication service impl.
*/
public CentralAuthenticationServiceImpl() {
super();
}
/**
* Build the central authentication service implementation.
*
* @param ticketRegistry the tickets registry.
* @param ticketFactory the ticket factory
* @param servicesManager the services manager.
* @param logoutManager the logout manager.
*/
public CentralAuthenticationServiceImpl(
final TicketRegistry ticketRegistry,
final TicketFactory ticketFactory,
final ServicesManager servicesManager,
final LogoutManager logoutManager) {
super(ticketRegistry, ticketFactory, servicesManager, logoutManager);
}
/**
* {@inheritDoc}
* Destroy a TicketGrantingTicket and perform back channel logout. This has the effect of invalidating any
* Ticket that was derived from the TicketGrantingTicket being destroyed. May throw an
* {@link IllegalArgumentException} if the TicketGrantingTicket ID is null.
*
* @param ticketGrantingTicketId the id of the ticket we want to destroy
* @return the logout requests.
*/
@Audit(
action = "TICKET_GRANTING_TICKET_DESTROYED",
actionResolverName = "DESTROY_TICKET_GRANTING_TICKET_RESOLVER",
resourceResolverName = "DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER")
@Timed(name = "DESTROY_TICKET_GRANTING_TICKET_TIMER")
@Metered(name = "DESTROY_TICKET_GRANTING_TICKET_METER")
@Counted(name = "DESTROY_TICKET_GRANTING_TICKET_COUNTER", monotonic = true)
@Override
public List destroyTicketGrantingTicket(@NotNull final String ticketGrantingTicketId) {
try {
logger.debug("Removing ticket [{}] from registry...", ticketGrantingTicketId);
final TicketGrantingTicket ticket = getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
logger.debug("Ticket found. Processing logout requests and then deleting the ticket...");
final List logoutRequests = logoutManager.performLogout(ticket);
this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
/*------------start-------TODO-------增加向消息队列中发送ticketId--------------------------*/
JSONObject jsonObject = new JSONObject();
JSONArray ticketIds = new JSONArray();
jsonObject.put("ticketIds",ticketIds);
for (LogoutRequest logoutRequest : logoutRequests) {
ticketIds.add(logoutRequest.getTicketId());
}
template.convertAndSend(jsonObject.toJSONString());
/*-------------end--------TODO-------增加向消息队列中发送ticketId--------------------------*/
doPublishEvent(new CasTicketGrantingTicketDestroyedEvent(this, ticket));
return logoutRequests;
} catch (final InvalidTicketException e) {
logger.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId);
}
return Collections.emptyList();
}
@Audit(
action = "SERVICE_TICKET",
actionResolverName = "GRANT_SERVICE_TICKET_RESOLVER",
resourceResolverName = "GRANT_SERVICE_TICKET_RESOURCE_RESOLVER")
@Timed(name = "GRANT_SERVICE_TICKET_TIMER")
@Metered(name = "GRANT_SERVICE_TICKET_METER")
@Counted(name = "GRANT_SERVICE_TICKET_COUNTER", monotonic = true)
@Override
public ServiceTicket grantServiceTicket(
final String ticketGrantingTicketId,
final Service service, final AuthenticationContext context)
throws AuthenticationException, AbstractTicketException {
logger.debug("Attempting to get ticket id {} to create service ticket", ticketGrantingTicketId);
final TicketGrantingTicket ticketGrantingTicket = getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
verifyRegisteredServiceProperties(registeredService, service);
evaluatePossibilityOfMixedPrincipals(context, ticketGrantingTicket);
if (ticketGrantingTicket.getCountOfUses() > 0 && !registeredService.getAccessStrategy().isServiceAccessAllowedForSso()) {
logger.warn("Service [{}] is not allowed to use SSO.", service.getId());
throw new UnauthorizedSsoServiceException();
}
evaluateProxiedServiceIfNeeded(service, ticketGrantingTicket, registeredService);
// Perform security policy check by getting the authentication that satisfies the configured policy
// This throws if no suitable policy is found
logger.debug("Checking for authentication policy satisfaction...");
getAuthenticationSatisfiedByPolicy(ticketGrantingTicket.getRoot(), new ServiceContext(service, registeredService));
final List authentications = ticketGrantingTicket.getChainedAuthentications();
final Principal principal = authentications.get(authentications.size() - 1).getPrincipal();
logger.debug("Located principal {} for service ticket creation", principal);
final RegisteredServiceAttributeReleasePolicy releasePolicy = registeredService.getAttributeReleasePolicy();
final Map principalAttrs;
if (releasePolicy != null) {
principalAttrs = releasePolicy.getAttributes(principal);
} else {
principalAttrs = new HashMap<>();
}
if (!registeredService.getAccessStrategy().doPrincipalAttributesAllowServiceAccess(principal.getId(), principalAttrs)) {
logger.warn("Cannot grant service ticket because Service [{}] is not authorized for use by [{}].",
service.getId(), principal);
throw new UnauthorizedServiceForPrincipalException();
}
final ServiceTicketFactory factory = this.ticketFactory.get(ServiceTicket.class);
final ServiceTicket serviceTicket = factory.create(ticketGrantingTicket, service,
context != null && context.isCredentialProvided());
logger.info("Granted ticket [{}] for service [{}] and principal [{}]",
serviceTicket.getId(), service.getId(), principal.getId());
this.ticketRegistry.addTicket(serviceTicket);
logger.debug("Added service ticket {} to ticket registry", serviceTicket.getId());
doPublishEvent(new CasServiceTicketGrantedEvent(this, ticketGrantingTicket, serviceTicket));
return serviceTicket;
}
/**
* Always keep track of a single authentication object,
* as opposed to keeping a history of all. This helps with
* memory consumption. Note that supplemental authentications
* are to be removed.
*
* @param context authentication context
* @param ticketGrantingTicket the tgt
* @return the processed authentication in the current context
* @throws MixedPrincipalException in case there is a principal mismatch between TGT and the current authN.
*/
private Authentication evaluatePossibilityOfMixedPrincipals(final AuthenticationContext context,
final TicketGrantingTicket ticketGrantingTicket)
throws MixedPrincipalException {
Authentication currentAuthentication = null;
if (context != null) {
currentAuthentication = context.getAuthentication();
if (currentAuthentication != null) {
final Authentication original = ticketGrantingTicket.getAuthentication();
if (!currentAuthentication.getPrincipal().equals(original.getPrincipal())) {
logger.debug("Principal associated with current authentication {} does not match "
+ " the principal {} associated with the original authentication",
currentAuthentication.getPrincipal(), original.getPrincipal());
throw new MixedPrincipalException(
currentAuthentication, currentAuthentication.getPrincipal(), original.getPrincipal());
}
ticketGrantingTicket.getSupplementalAuthentications().clear();
ticketGrantingTicket.getSupplementalAuthentications().add(currentAuthentication);
logger.debug("Added authentication to the collection of supplemental authentications");
}
}
return currentAuthentication;
}
@Audit(
action = "PROXY_TICKET",
actionResolverName = "GRANT_PROXY_TICKET_RESOLVER",
resourceResolverName = "GRANT_PROXY_TICKET_RESOURCE_RESOLVER")
@Timed(name = "GRANT_PROXY_TICKET_TIMER")
@Metered(name = "GRANT_PROXY_TICKET_METER")
@Counted(name = "GRANT_PROXY_TICKET_COUNTER", monotonic = true)
@Override
public ProxyTicket grantProxyTicket(final String proxyGrantingTicket, final Service service)
throws AbstractTicketException {
final ProxyGrantingTicket proxyGrantingTicketObject = getTicket(proxyGrantingTicket, ProxyGrantingTicket.class);
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
verifyRegisteredServiceProperties(registeredService, service);
if (!registeredService.getAccessStrategy().isServiceAccessAllowedForSso()) {
logger.warn("Service [{}] is not allowed to use SSO.", service.getId());
throw new UnauthorizedSsoServiceException();
}
evaluateProxiedServiceIfNeeded(service, proxyGrantingTicketObject, registeredService);
// Perform security policy check by getting the authentication that satisfies the configured policy
// This throws if no suitable policy is found
getAuthenticationSatisfiedByPolicy(proxyGrantingTicketObject.getRoot(), new ServiceContext(service, registeredService));
final List authentications = proxyGrantingTicketObject.getChainedAuthentications();
final Principal principal = authentications.get(authentications.size() - 1).getPrincipal();
final RegisteredServiceAttributeReleasePolicy releasePolicy = registeredService.getAttributeReleasePolicy();
final Map principalAttrs;
if (releasePolicy != null) {
principalAttrs = releasePolicy.getAttributes(principal);
} else {
principalAttrs = new HashMap<>();
}
if (!registeredService.getAccessStrategy().doPrincipalAttributesAllowServiceAccess(principal.getId(), principalAttrs)) {
logger.warn("Cannot grant proxy ticket because Service [{}] is not authorized for use by [{}].",
service.getId(), principal);
throw new UnauthorizedServiceForPrincipalException();
}
final ProxyTicketFactory factory = this.ticketFactory.get(ProxyTicket.class);
final ProxyTicket proxyTicket = factory.create(proxyGrantingTicketObject, service);
this.ticketRegistry.addTicket(proxyTicket);
logger.info("Granted ticket [{}] for service [{}] for user [{}]",
proxyTicket.getId(), service.getId(), principal.getId());
doPublishEvent(new CasProxyTicketGrantedEvent(this, proxyGrantingTicketObject, proxyTicket));
return proxyTicket;
}
@Audit(
action = "PROXY_GRANTING_TICKET",
actionResolverName = "CREATE_PROXY_GRANTING_TICKET_RESOLVER",
resourceResolverName = "CREATE_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER")
@Timed(name = "CREATE_PROXY_GRANTING_TICKET_TIMER")
@Metered(name = "CREATE_PROXY_GRANTING_TICKET_METER")
@Counted(name = "CREATE_PROXY_GRANTING_TICKET_COUNTER", monotonic = true)
@Override
public ProxyGrantingTicket createProxyGrantingTicket(final String serviceTicketId, final AuthenticationContext context)
throws AuthenticationException, AbstractTicketException {
final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
if (serviceTicket == null || serviceTicket.isExpired()) {
logger.debug("ServiceTicket [{}] has expired or cannot be found in the ticket registry", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
final RegisteredService registeredService = this.servicesManager
.findServiceBy(serviceTicket.getService());
verifyRegisteredServiceProperties(registeredService, serviceTicket.getService());
if (!registeredService.getProxyPolicy().isAllowedToProxy()) {
logger.warn("ServiceManagement: Service [{}] attempted to proxy, but is not allowed.", serviceTicket.getService().getId());
throw new UnauthorizedProxyingException();
}
final Authentication authentication = context.getAuthentication();
final ProxyGrantingTicketFactory factory = this.ticketFactory.get(ProxyGrantingTicket.class);
final ProxyGrantingTicket proxyGrantingTicket = factory.create(serviceTicket, authentication);
logger.debug("Generated proxy granting ticket [{}] based off of [{}]", proxyGrantingTicket, serviceTicketId);
this.ticketRegistry.addTicket(proxyGrantingTicket);
doPublishEvent(new CasProxyGrantingTicketCreatedEvent(this, proxyGrantingTicket));
return proxyGrantingTicket;
}
@Audit(
action = "SERVICE_TICKET_VALIDATE",
actionResolverName = "VALIDATE_SERVICE_TICKET_RESOLVER",
resourceResolverName = "VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER")
@Timed(name = "VALIDATE_SERVICE_TICKET_TIMER")
@Metered(name = "VALIDATE_SERVICE_TICKET_METER")
@Counted(name = "VALIDATE_SERVICE_TICKET_COUNTER", monotonic = true)
@Override
public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws AbstractTicketException {
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
verifyRegisteredServiceProperties(registeredService, service);
final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
if (serviceTicket == null) {
logger.info("Service ticket [{}] does not exist.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
try {
synchronized (serviceTicket) {
if (serviceTicket.isExpired()) {
logger.info("ServiceTicket [{}] has expired.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
if (!serviceTicket.isValidFor(service)) {
logger.error("Service ticket [{}] with service [{}] does not match supplied service [{}]",
serviceTicketId, serviceTicket.getService().getId(), service);
throw new UnrecognizableServiceForServiceTicketValidationException(serviceTicket.getService());
}
}
final TicketGrantingTicket root = serviceTicket.getGrantingTicket().getRoot();
final Authentication authentication = getAuthenticationSatisfiedByPolicy(
root, new ServiceContext(serviceTicket.getService(), registeredService));
final Principal principal = authentication.getPrincipal();
final RegisteredServiceAttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy();
logger.debug("Attribute policy [{}] is associated with service [{}]", attributePolicy, registeredService);
@SuppressWarnings("unchecked")
final Map attributesToRelease = attributePolicy != null
? attributePolicy.getAttributes(principal) : Collections.EMPTY_MAP;
final String principalId = registeredService.getUsernameAttributeProvider().resolveUsername(principal, service);
final Principal modifiedPrincipal = this.principalFactory.createPrincipal(principalId, attributesToRelease);
final AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(authentication);
builder.setPrincipal(modifiedPrincipal);
final Assertion assertion = new ImmutableAssertion(
builder.build(),
serviceTicket.getGrantingTicket().getChainedAuthentications(),
serviceTicket.getService(),
serviceTicket.isFromNewLogin());
doPublishEvent(new CasServiceTicketValidatedEvent(this, serviceTicket, assertion));
return assertion;
} finally {
if (serviceTicket.isExpired()) {
this.ticketRegistry.deleteTicket(serviceTicketId);
}
}
}
@Audit(
action = "TICKET_GRANTING_TICKET",
actionResolverName = "CREATE_TICKET_GRANTING_TICKET_RESOLVER",
resourceResolverName = "CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER")
@Timed(name = "CREATE_TICKET_GRANTING_TICKET_TIMER")
@Metered(name = "CREATE_TICKET_GRANTING_TICKET_METER")
@Counted(name = "CREATE_TICKET_GRANTING_TICKET_COUNTER", monotonic = true)
@Override
public TicketGrantingTicket createTicketGrantingTicket(final AuthenticationContext context)
throws AuthenticationException, AbstractTicketException {
final Authentication authentication = context.getAuthentication();
final TicketGrantingTicketFactory factory = this.ticketFactory.get(TicketGrantingTicket.class);
final TicketGrantingTicket ticketGrantingTicket = factory.create(authentication);
this.ticketRegistry.addTicket(ticketGrantingTicket);
doPublishEvent(new CasTicketGrantingTicketCreatedEvent(this, ticketGrantingTicket));
return ticketGrantingTicket;
}
/**
* 测试rabbitMq
*/
/*@PostConstruct
public void aaa(){
for (int i = 0; i < 10; i++) {
JSONObject ticketObj = new JSONObject();
ticketObj.put("ticketId",i);
template.convertAndSend(ticketObj.toJSONString());
}
}*/
}r
然后重写客户管的销毁部分代码。
package com.yzb.mq;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.jasig.cas.client.session.SingleSignOutHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
/**
* @author xiaoh
* @version [版本号, 2018/4/26 20:18]
* @Description:
* @versio 1.0
* 西安优智泊物联网技术服务有限公司
* Copyright (c) 2017 All Rights Reserved.
*/
@Component
public class CasQueue implements InitializingBean {
@Autowired
private MqRadioService mqRadioService;
protected final transient Logger logger = LoggerFactory.getLogger(this.getClass());
private SingleSignOutHandler singleSignOutHandler;
public void onAccessTokenDes(JSONObject message) {
JSONArray ticketIds = message.getJSONArray("ticketIds");
for (Object ticketId : ticketIds) {
final HttpSession session = singleSignOutHandler.getSessionMappingStorage().removeSessionByMappingId((String) ticketId);
if (session != null) {
final String sessionID = session.getId();
logger.debug("Invalidating session [{}] for token [{}]", sessionID, ticketId);
try {
session.invalidate();
} catch (final IllegalStateException e) {
logger.debug("Error invalidating session.", e);
}
}
}
}
public void setSingleSignOutHandler(SingleSignOutHandler singleSignOutHandler) {
this.singleSignOutHandler = singleSignOutHandler;
}
@Override
public void afterPropertiesSet() throws Exception {
mqRadioService.registerRadioListener(this::onAccessTokenDes, "cas_exchange");
}
}