1:假设有web服务器A,web服务器B,和CasServer。
A(www.a.com) B(www.b.com) CasServer(www.cas.com)
2:当用户访问A的受保护资源时,如果没有登录,则A会让客户端浏览器重定向到CAS SERVER。
假设:https://www.cas.com/server/login?service=http://www.a.com/client/cas
其中红色为cas的登录地址,黄色为登录成功后的重定向地址。
3:客户在CasServer的登录页面进行登录,如果登录成功则CasServer会让客户端浏览器重定向到
http://www.a.com/client/cas?ticket=ST-1-eh2cIo92F9syvoMs5DOg-cas01.example.org 这个地址
附加了一个ticket,同时会在Cookie中设置一个CASTGC,该cookie是CasServer(www.cas.com)的cookie,只有访问这个网站才会携带这个cookie过去。
4:客户端需要一个filter,拦截client/cas。
然后获取到ticket,然后通过https的方式访问CasServer进行验证,验证成功返回用户身份信息,完成登录。
5:假设用户直接访问 B的受保护资源,显然B此时是未登录的。所以流程跟2一样,会让浏览器重定向到CasServer去登录。
https://www.cas.com/server/login?service=http://www.b.com/client/cas
但此次重定向时请求中多了一个cookit(CASTGC),CasServer会发现这个cookie对应的用户已经登录过了,那么此时不需要显示登录页面,而直接让客户端浏览器重定向到http://www.b.com/client/cas?ticket=ST-1-eh2cIo92F9syvoMs5DOg-cas01.example.org。
B站点会用ticket去casCerver换取身份信息,完成登录。
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotNull;
import org.jasig.cas.authentication.Authentication;
import org.jasig.cas.authentication.AuthenticationManager;
import org.jasig.cas.authentication.MutableAuthentication;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.principal.Credentials;
import org.jasig.cas.authentication.principal.PersistentIdGenerator;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.jasig.cas.services.RegisteredService;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.services.UnauthorizedProxyingException;
import org.jasig.cas.services.UnauthorizedServiceException;
import org.jasig.cas.services.UnauthorizedSsoServiceException;
import org.jasig.cas.ticket.ExpirationPolicy;
import org.jasig.cas.ticket.InvalidTicketException;
import org.jasig.cas.ticket.ServiceTicket;
import org.jasig.cas.ticket.TicketCreationException;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.TicketGrantingTicketImpl;
import org.jasig.cas.ticket.TicketValidationException;
import org.jasig.cas.ticket.registry.TicketRegistry;
import org.jasig.cas.util.UniqueTicketIdGenerator;
import org.jasig.cas.validation.Assertion;
import org.jasig.cas.validation.ImmutableAssertionImpl;
import org.perf4j.aop.Profiled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import com.github.inspektr.audit.annotation.Audit;
/**
* Concrete implementation of a CentralAuthenticationService, and also the
* central, organizing component of CAS's internal implementation.
* <p>
* This class is threadsafe.
* <p>
* This class has the following properties that must be set:
* <ul>
* <li> <code>ticketRegistry</code> - The Ticket Registry to maintain the list
* of available tickets.</li>
* <li> <code>serviceTicketRegistry</code> - Provides an alternative to configure separate registries for TGTs and ST in order to store them
* in different locations (i.e. long term memory or short-term)</li>
* <li> <code>authenticationManager</code> - The service that will handle
* authentication.</li>
* <li> <code>ticketGrantingTicketUniqueTicketIdGenerator</code> - Plug in to
* generate unique secure ids for TicketGrantingTickets.</li>
* <li> <code>serviceTicketUniqueTicketIdGenerator</code> - Plug in to
* generate unique secure ids for ServiceTickets.</li>
* <li> <code>ticketGrantingTicketExpirationPolicy</code> - The expiration
* policy for TicketGrantingTickets.</li>
* <li> <code>serviceTicketExpirationPolicy</code> - The expiration policy for
* ServiceTickets.</li>
* </ul>
*
* @author William G. Thompson, Jr.
* @author Scott Battaglia
* @author Dmitry Kopylenko
* @version $Revision: 1.16 $ $Date: 2007/04/24 18:11:36 $
* @since 3.0
*/
public final class CentralAuthenticationServiceImpl implements CentralAuthenticationService {
/** Log instance for logging events, info, warnings, errors, etc. */
private final Logger log = LoggerFactory.getLogger(this.getClass());
/** TicketRegistry for storing and retrieving tickets as needed. */
@NotNull
private TicketRegistry ticketRegistry;
/** New Ticket Registry for storing and retrieving services tickets. Can point to the same one as the ticketRegistry variable. */
@NotNull
private TicketRegistry serviceTicketRegistry;
/**
* AuthenticationManager for authenticating credentials for purposes of
* obtaining tickets.
*/
@NotNull
private AuthenticationManager authenticationManager;
/**
* UniqueTicketIdGenerator to generate ids for TicketGrantingTickets
* created.
*/
@NotNull
private UniqueTicketIdGenerator ticketGrantingTicketUniqueTicketIdGenerator;
/** Map to contain the mappings of service->UniqueTicketIdGenerators */
@NotNull
private Map<String, UniqueTicketIdGenerator> uniqueTicketIdGeneratorsForService;
/** Expiration policy for ticket granting tickets. */
@NotNull
private ExpirationPolicy ticketGrantingTicketExpirationPolicy;
/** ExpirationPolicy for Service Tickets. */
@NotNull
private ExpirationPolicy serviceTicketExpirationPolicy;
/** Implementation of Service Manager */
@NotNull
private ServicesManager servicesManager;
/** Encoder to generate PseudoIds. */
@NotNull
private PersistentIdGenerator persistentIdGenerator = new ShibbolethCompatiblePersistentIdGenerator();
/**
* Implementation of destoryTicketGrantingTicket expires the ticket provided
* and removes it from the TicketRegistry.
*
* @throws IllegalArgumentException if the TicketGrantingTicket ID is null.
*/
@Audit(
action="TICKET_GRANTING_TICKET_DESTROYED",
actionResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOLVER",
resourceResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER")
@Profiled(tag = "DESTROY_TICKET_GRANTING_TICKET",logFailuresSeparately = false)
@Transactional(readOnly = false)
public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
Assert.notNull(ticketGrantingTicketId);
if (log.isDebugEnabled()) {
log.debug("Removing ticket [" + ticketGrantingTicketId + "] from registry.");
}
final TicketGrantingTicket ticket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
if (ticket == null) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Ticket found. Expiring and then deleting.");
}
ticket.expire();
this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
}
/**
* @throws IllegalArgumentException if TicketGrantingTicket ID, Credentials
* or Service are null.
*/
@Audit(
action="SERVICE_TICKET",
actionResolverName="GRANT_SERVICE_TICKET_RESOLVER",
resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER")
@Profiled(tag="GRANT_SERVICE_TICKET", logFailuresSeparately = false)
@Transactional(readOnly = false)
public String grantServiceTicket(final String ticketGrantingTicketId, final Service service, final Credentials credentials) throws TicketException {
Assert.notNull(ticketGrantingTicketId, "ticketGrantingticketId cannot be null");
Assert.notNull(service, "service cannot be null");
//从缓存中获取TGT
final TicketGrantingTicket ticketGrantingTicket;
ticketGrantingTicket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
//TGT不存在则抛出异常
if (ticketGrantingTicket == null) {
throw new InvalidTicketException();
}
//检查tgt是否过期
synchronized (ticketGrantingTicket) {
if (ticketGrantingTicket.isExpired()) {
this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
throw new InvalidTicketException();
}
}
//这里的service代表客户端信息,检查客户端身份,是否允许接入
final RegisteredService registeredService = this.servicesManager
.findServiceBy(service);
//如果返回信息为空 或者 客户端不允许接入CAS 抛出异常
if (registeredService == null || !registeredService.isEnabled()) {
log.warn("ServiceManagement: Unauthorized Service Access. Service [" + service.getId() + "] not found in Service Registry.");
throw new UnauthorizedServiceException();
}
//判断客户端是否允许接入SSO Session 单点登录 大概的意思就是当前的客户端必须重新登录,不能使用旧的TGT凭据
if (!registeredService.isSsoEnabled() && credentials == null
&& ticketGrantingTicket.getCountOfUses() > 0) {
log.warn("ServiceManagement: Service Not Allowed to use SSO. Service [" + service.getId() + "]");
throw new UnauthorizedSsoServiceException();
}
//CAS-1019
final List<Authentication> authns = ticketGrantingTicket.getChainedAuthentications();
if(authns.size() > 1) {
if (!registeredService.isAllowedToProxy()) {
final String message = String.format("ServiceManagement: Service Attempted to Proxy, but is not allowed. Service: [%s] | Registered Service: [%s]", service.getId(), registeredService.toString());
log.warn(message);
throw new UnauthorizedProxyingException(message);
}
}
if (credentials != null) {
try {
final Authentication authentication = this.authenticationManager
.authenticate(credentials);
final Authentication originalAuthentication = ticketGrantingTicket.getAuthentication();
if (!(authentication.getPrincipal().equals(originalAuthentication.getPrincipal()) && authentication.getAttributes().equals(originalAuthentication.getAttributes()))) {
throw new TicketCreationException();
}
} catch (final AuthenticationException e) {
throw new TicketCreationException(e);
}
}
//ID生成器
// this code is a bit brittle by depending on the class name. Future versions (i.e. CAS4 will know inherently how to identify themselves)
final UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator = this.uniqueTicketIdGeneratorsForService
.get(service.getClass().getName());
//创建一个ST
final ServiceTicket serviceTicket = ticketGrantingTicket
.grantServiceTicket(serviceTicketUniqueTicketIdGenerator
.getNewTicketId(ServiceTicket.PREFIX), service,
this.serviceTicketExpirationPolicy, credentials != null);
//ST放入缓存
this.serviceTicketRegistry.addTicket(serviceTicket);
if (log.isInfoEnabled()) {
final List<Authentication> authentications = serviceTicket.getGrantingTicket().getChainedAuthentications();
final String formatString = "Granted %s ticket [%s] for service [%s] for user [%s]";
final String type;
final String principalId = authentications.get(authentications.size()-1).getPrincipal().getId();
if (authentications.size() == 1) {
type = "service";
} else {
type = "proxy";
}
log.info(String.format(formatString, type, serviceTicket.getId(), service.getId(), principalId));
}
return serviceTicket.getId();
}
@Audit(
action="SERVICE_TICKET",
actionResolverName="GRANT_SERVICE_TICKET_RESOLVER",
resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER")
@Profiled(tag = "GRANT_SERVICE_TICKET",logFailuresSeparately = false)
@Transactional(readOnly = false)
public String grantServiceTicket(final String ticketGrantingTicketId,
final Service service) throws TicketException {
return this.grantServiceTicket(ticketGrantingTicketId, service, null);
}
/**
* @throws IllegalArgumentException if the ServiceTicketId or the
* Credentials are null.
*/
@Audit(
action="PROXY_GRANTING_TICKET",
actionResolverName="GRANT_PROXY_GRANTING_TICKET_RESOLVER",
resourceResolverName="GRANT_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER")
@Profiled(tag="GRANT_PROXY_GRANTING_TICKET",logFailuresSeparately = false)
@Transactional(readOnly = false)
public String delegateTicketGrantingTicket(final String serviceTicketId,
final Credentials credentials) throws TicketException {
Assert.notNull(serviceTicketId, "serviceTicketId cannot be null");
Assert.notNull(credentials, "credentials cannot be null");
try {
final Authentication authentication = this.authenticationManager
.authenticate(credentials);
final ServiceTicket serviceTicket;
serviceTicket = (ServiceTicket) this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
if (serviceTicket == null || serviceTicket.isExpired()) {
throw new InvalidTicketException();
}
final RegisteredService registeredService = this.servicesManager
.findServiceBy(serviceTicket.getService());
if (registeredService == null || !registeredService.isEnabled()
|| !registeredService.isAllowedToProxy()) {
log.warn("ServiceManagement: Service Attempted to Proxy, but is not allowed. Service: [" + serviceTicket.getService().getId() + "]");
throw new UnauthorizedProxyingException();
}
final TicketGrantingTicket ticketGrantingTicket = serviceTicket
.grantTicketGrantingTicket(
this.ticketGrantingTicketUniqueTicketIdGenerator
.getNewTicketId(TicketGrantingTicket.PREFIX),
authentication, this.ticketGrantingTicketExpirationPolicy);
this.ticketRegistry.addTicket(ticketGrantingTicket);
return ticketGrantingTicket.getId();
} catch (final AuthenticationException e) {
throw new TicketCreationException(e);
}
}
/**
* @throws IllegalArgumentException if the ServiceTicketId or the Service
* are null.
*/
@Audit(
action="SERVICE_TICKET_VALIDATE",
actionResolverName="VALIDATE_SERVICE_TICKET_RESOLVER",
resourceResolverName="VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER")
@Profiled(tag="VALIDATE_SERVICE_TICKET",logFailuresSeparately = false)
@Transactional(readOnly = false)
public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException {
Assert.notNull(serviceTicketId, "serviceTicketId cannot be null");
Assert.notNull(service, "service cannot be null");
//从缓存中获取ST
final ServiceTicket serviceTicket = (ServiceTicket) this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
//根据service拿到客户端信息
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
//如果客户端不存在或客户端不允许接入cas
if (registeredService == null || !registeredService.isEnabled()) {
log.warn("ServiceManagement: Service does not exist is not enabled, and thus not allowed to validate tickets. Service: [" + service.getId() + "]");
throw new UnauthorizedServiceException("Service not allowed to validate tickets.");
}
//如果st不存在
if (serviceTicket == null) {
log.info("ServiceTicket [" + serviceTicketId + "] does not exist.");
throw new InvalidTicketException();
}
try {
//判断ST是否过期
synchronized (serviceTicket) {
if (serviceTicket.isExpired()) {
log.info("ServiceTicket [" + serviceTicketId + "] has expired.");
throw new InvalidTicketException();
}
//更新ST状态 countOfUses与使用时间 并且与之前的service对比 主要对比回调路径
if (!serviceTicket.isValidFor(service)) {
log.error("ServiceTicket [" + serviceTicketId + "] with service [" + serviceTicket.getService().getId() + " does not match supplied service [" + service + "]");
throw new TicketValidationException(serviceTicket.getService());
}
}
final int authenticationChainSize = serviceTicket
.getGrantingTicket().getChainedAuthentications().size();
final Authentication authentication = serviceTicket
.getGrantingTicket().getChainedAuthentications().get(
authenticationChainSize - 1);
//获取用户信息
final Principal principal = authentication.getPrincipal();
//如果允许匿名登录则返回随机ID 否则返回用户ID
final String principalId = registeredService.isAnonymousAccess()
? this.persistentIdGenerator.generate(principal, serviceTicket
.getService()) : principal.getId();
final Authentication authToUse;
//不忽略用户其它属性
if (!registeredService.isIgnoreAttributes()) {
final Map<String, Object> attributes = new HashMap<String, Object>();
//客户端配置的属性key
for (final String attribute : registeredService
.getAllowedAttributes()) {
//从用户的属性信息中根据key查找value
final Object value = principal.getAttributes().get(
attribute);
//存入临时map
if (value != null) {
attributes.put(attribute, value);
}
}
//创建一个新的用户对象 放入ID和用户属性
final Principal modifiedPrincipal = new SimplePrincipal(
principalId, attributes);
final MutableAuthentication mutableAuthentication = new MutableAuthentication(
modifiedPrincipal, authentication.getAuthenticatedDate());
mutableAuthentication.getAttributes().putAll(
authentication.getAttributes());
mutableAuthentication.getAuthenticatedDate().setTime(
authentication.getAuthenticatedDate().getTime());
authToUse = mutableAuthentication;
} else {
authToUse = authentication;
}
final List<Authentication> authentications = new ArrayList<Authentication>();
for (int i = 0; i < authenticationChainSize - 1; i++) {
authentications.add(serviceTicket.getGrantingTicket().getChainedAuthentications().get(i));
}
authentications.add(authToUse);
return new ImmutableAssertionImpl(authentications, serviceTicket.getService(), serviceTicket.isFromNewLogin());
} finally {
//最后让st过期,只使用一次
if (serviceTicket.isExpired()) {
//在缓存中清除
this.serviceTicketRegistry.deleteTicket(serviceTicketId);
}
}
}
/**
* @throws IllegalArgumentException if the credentials are null.
*/
@Audit(
action="TICKET_GRANTING_TICKET",
actionResolverName="CREATE_TICKET_GRANTING_TICKET_RESOLVER",
resourceResolverName="CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER")
@Profiled(tag = "CREATE_TICKET_GRANTING_TICKET", logFailuresSeparately = false)
@Transactional(readOnly = false)
public String createTicketGrantingTicket(final Credentials credentials) throws TicketCreationException {
Assert.notNull(credentials, "credentials cannot be null");
try {
//登录认证 成功返回Authentication对象 失败抛出异常
final Authentication authentication = this.authenticationManager
.authenticate(credentials);
//创建TGT 传入唯一的ID,Authentication对象,和ticket过期策略
final TicketGrantingTicket ticketGrantingTicket = new TicketGrantingTicketImpl(
this.ticketGrantingTicketUniqueTicketIdGenerator
.getNewTicketId(TicketGrantingTicket.PREFIX),
authentication, this.ticketGrantingTicketExpirationPolicy);
//将TGT放入缓存 默认 DefaultTicketRegistry 内嵌 ConcurrentHashMap 实现
this.ticketRegistry.addTicket(ticketGrantingTicket);
return ticketGrantingTicket.getId();
} catch (final AuthenticationException e) {
throw new TicketCreationException(e);
}
}
/**
* Method to set the TicketRegistry.
*
* @param ticketRegistry the TicketRegistry to set.
*/
public void setTicketRegistry(final TicketRegistry ticketRegistry) {
this.ticketRegistry = ticketRegistry;
if (this.serviceTicketRegistry == null) {
this.serviceTicketRegistry = ticketRegistry;
}
}
public void setServiceTicketRegistry(final TicketRegistry serviceTicketRegistry) {
this.serviceTicketRegistry = serviceTicketRegistry;
}
/**
* Method to inject the AuthenticationManager into the class.
*
* @param authenticationManager The authenticationManager to set.
*/
public void setAuthenticationManager(
final AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* Method to inject the TicketGrantingTicket Expiration Policy.
*
* @param ticketGrantingTicketExpirationPolicy The
* ticketGrantingTicketExpirationPolicy to set.
*/
public void setTicketGrantingTicketExpirationPolicy(
final ExpirationPolicy ticketGrantingTicketExpirationPolicy) {
this.ticketGrantingTicketExpirationPolicy = ticketGrantingTicketExpirationPolicy;
}
/**
* Method to inject the Unique Ticket Id Generator into the class.
*
* @param uniqueTicketIdGenerator The uniqueTicketIdGenerator to use
*/
public void setTicketGrantingTicketUniqueTicketIdGenerator(
final UniqueTicketIdGenerator uniqueTicketIdGenerator) {
this.ticketGrantingTicketUniqueTicketIdGenerator = uniqueTicketIdGenerator;
}
/**
* Method to inject the TicketGrantingTicket Expiration Policy.
*
* @param serviceTicketExpirationPolicy The serviceTicketExpirationPolicy to
* set.
*/
public void setServiceTicketExpirationPolicy(
final ExpirationPolicy serviceTicketExpirationPolicy) {
this.serviceTicketExpirationPolicy = serviceTicketExpirationPolicy;
}
public void setUniqueTicketIdGeneratorsForService(
final Map<String, UniqueTicketIdGenerator> uniqueTicketIdGeneratorsForService) {
this.uniqueTicketIdGeneratorsForService = uniqueTicketIdGeneratorsForService;
}
public void setServicesManager(final ServicesManager servicesManager) {
this.servicesManager = servicesManager;
}
public void setPersistentIdGenerator(
final PersistentIdGenerator persistentIdGenerator) {
this.persistentIdGenerator = persistentIdGenerator;
}
}