CAS的票据类的设计
cas的核心服务内容是登陆授权的赋予和验证,而其中在服务过程中每一次操作都用到了票据ticket的验证。说白了就是ticket一系列的增删改查操作。
我们知道cas的ticket主要分成两种TGT(ticketgrantticket)和ST(serviceticket)。TGT负责用户认证而ST负责用户授权。但他们的一些基础行为都很类似所以,他们拥有同一个接口(Ticket)。
public interface Ticket extends Serializable {
/**
* Method to retrieve the id.
*
* @return the id
*/
String getId();
/**
* Determines if the ticket is expired. Most common implementations might
* collaborate with <i>ExpirationPolicy </i> strategy.
*
* @see org.jasig.cas.ticket.ExpirationPolicy
*/
boolean isExpired();
/**
* Method to retrive the TicketGrantingTicket that granted this ticket.
*
* @return the ticket or null if it has no parent
*/
TicketGrantingTicket getGrantingTicket();
/**
* Method to return the time the Ticket was created.
*
* @return the time the ticket was created.
*/
long getCreationTime();
/**
* Returns the number of times this ticket was used.
* @return
*/
int getCountOfUses();
}
ticket在这个看作是数据对象,而对于这些ticket进行操作的工作交给了TicketRegistry的实现类,我们先来看一些他的借口。
public interface TicketRegistry {
/**
* Add a ticket to the registry. Ticket storage is based on the ticket id.
*
* @param ticket The ticket we wish to add to the cache.
*/
void addTicket(Ticket ticket);
/**
* Retrieve a ticket from the registry. If the ticket retrieved does not
* match the expected class, an InvalidTicketException is thrown.
*
* @param ticketId the id of the ticket we wish to retrieve.
* @param clazz The expected class of the ticket we wish to retrieve.
* @return the requested ticket.
* @throws InvalidTicketClassException if the ticket does not match the
* class provided.
*/
Ticket getTicket(String ticketId, Class<? extends Ticket> clazz);
/**
* Retrieve a ticket from the registry.
*
* @param ticketId the id of the ticket we wish to retrieve
* @return the requested ticket.
*/
Ticket getTicket(String ticketId);
/**
* Remove a specific ticket from the registry.
*
* @param ticketId The id of the ticket to delete.
* @return true if the ticket was removed and false if the ticket did not
* exist.
*/
boolean deleteTicket(String ticketId);
/**
* Retrieve all tickets from the registry.
*
* @return collection of tickets currently stored in the registry. Tickets
* might or might not be valid i.e. expired.
*/
Collection<Ticket> getTickets();
}
方法很简单,基本上都是根据ticketid主键对ticket实体进行增删。
在来看一下他的实现类JpaTicketRegistry
public final class JpaTicketRegistry extends AbstractDistributedTicketRegistry implements TicketRegistryState {
@NotNull
@PersistenceContext
private EntityManager entityManager;
@NotNull
private String ticketGrantingTicketPrefix = "TGT";
protected void updateTicket(final Ticket ticket) {
entityManager.merge(ticket);
log.debug("Updated ticket [{}].", ticket);
}
@Transactional(readOnly = false)
public void addTicket(final Ticket ticket) {
entityManager.persist(ticket);
log.debug("Added ticket [{}] to registry.", ticket);
}
@Transactional(readOnly = false)
public boolean deleteTicket(final String ticketId) {
final Ticket ticket = getRawTicket(ticketId);
if (ticket == null) {
return false;
}
if (ticket instanceof ServiceTicket) {
removeTicket(ticket);
log.debug("Deleted ticket [{}] from the registry.", ticket);
return true;
}
deleteTicketAndChildren(ticket);
log.debug("Deleted ticket [{}] and its children from the registry.", ticket);
return true;
}
private void deleteTicketAndChildren(final Ticket ticket) {
final List<TicketGrantingTicketImpl> ticketGrantingTicketImpls = entityManager
.createQuery("select t from TicketGrantingTicketImpl t where t.ticketGrantingTicket.id = :id", TicketGrantingTicketImpl.class)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setParameter("id", ticket.getId())
.getResultList();
final List<ServiceTicketImpl> serviceTicketImpls = entityManager
.createQuery("select s from ServiceTicketImpl s where s.ticketGrantingTicket.id = :id", ServiceTicketImpl.class)
.setParameter("id", ticket.getId())
.getResultList();
for (final ServiceTicketImpl s : serviceTicketImpls) {
removeTicket(s);
}
for (final TicketGrantingTicketImpl t : ticketGrantingTicketImpls) {
deleteTicketAndChildren(t);
}
removeTicket(ticket);
}
private void removeTicket(final Ticket ticket) {
try {
if (log.isDebugEnabled()) {
final Date creationDate = new Date(ticket.getCreationTime());
log.debug("Removing Ticket [{}] created: {}", ticket, creationDate.toString());
}
entityManager.remove(ticket);
} catch (final Exception e) {
log.error("Error removing {} from registry.", ticket, e);
}
}
@Transactional(readOnly=true)
public Ticket getTicket(final String ticketId) {
return getProxiedTicketInstance(getRawTicket(ticketId));
}
private Ticket getRawTicket(final String ticketId) {
try {
if (ticketId.startsWith(this.ticketGrantingTicketPrefix)) {
return entityManager.find(TicketGrantingTicketImpl.class, ticketId, LockModeType.PESSIMISTIC_WRITE);
}
return entityManager.find(ServiceTicketImpl.class, ticketId);
} catch (final Exception e) {
log.error("Error getting ticket {} from registry.", ticketId, e);
}
return null;
}
@Transactional(readOnly=true)
public Collection<Ticket> getTickets() {
final List<TicketGrantingTicketImpl> tgts = entityManager
.createQuery("select t from TicketGrantingTicketImpl t", TicketGrantingTicketImpl.class)
.getResultList();
final List<ServiceTicketImpl> sts = entityManager
.createQuery("select s from ServiceTicketImpl s", ServiceTicketImpl.class)
.getResultList();
final List<Ticket> tickets = new ArrayList<Ticket>();
tickets.addAll(tgts);
tickets.addAll(sts);
return tickets;
}
public void setTicketGrantingTicketPrefix(final String ticketGrantingTicketPrefix) {
this.ticketGrantingTicketPrefix = ticketGrantingTicketPrefix;
}
@Override
protected boolean needsCallback() {
return false;
}
@Transactional(readOnly=true)
public int sessionCount() {
return countToInt(entityManager.createQuery("select count(t) from TicketGrantingTicketImpl t").getSingleResult());
}
@Transactional(readOnly=true)
public int serviceTicketCount() {
return countToInt(entityManager.createQuery("select count(t) from ServiceTicketImpl t").getSingleResult());
}
private int countToInt(final Object result) {
final int intval;
if (result instanceof Long) {
intval = ((Long) result).intValue();
} else if (result instanceof Integer) {
intval = (Integer) result;
} else {
// Must be a Number of some kind
intval = ((Number) result).intValue();
}
return intval;
}
}
实现类就有些复杂了,不光是具体实现了借口的一些基本方法,而且也对TGT和ST进行一系列的相关操作。
在类中我们看到updateticket和addticket方法比较简单,基本上只是调用jpa的api,将ticket入口。而getticket方法则不然。
在这个类中一个有两个获得ticket的方法,一是getRawTicket获得的是ticket实体类可以理解为数据库的源数据,二是getTicket他返回的是ticket的一个代理类。
这个代理类封装了一些ticket的方法。
public Ticket getTicket(final String ticketId) {
return getProxiedTicketInstance(getRawTicket(ticketId));
}
getProxiedTicketInstance是JpaTicketRegistry的父类方法
protected final Ticket getProxiedTicketInstance(final Ticket ticket) {
if (ticket == null) {
return null;
}
if (ticket instanceof TicketGrantingTicket) {
return new TicketGrantingTicketDelegator(this, (TicketGrantingTicket) ticket, needsCallback());
}
return new ServiceTicketDelegator(this, (ServiceTicket) ticket, needsCallback());
}
这里对ticket的类型进行判断,如果是TGT类型的则返回TGT的代理,如果是ST类型的则返回ST的代理。
先看一下TGT的代理类,它就被定义在这个父类里作为一个内部类。
private static final class TicketGrantingTicketDelegator extends TicketDelegator<TicketGrantingTicket> implements TicketGrantingTicket {
protected TicketGrantingTicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, final TicketGrantingTicket ticketGrantingTicket, final boolean callback) {
super(ticketRegistry, ticketGrantingTicket, callback);
}
public Authentication getAuthentication() {
return getTicket().getAuthentication();
}
public ServiceTicket grantServiceTicket(final String id, final Service service, final ExpirationPolicy expirationPolicy, final boolean credentialsProvided) {
final ServiceTicket t = this.getTicket().grantServiceTicket(id, service, expirationPolicy, credentialsProvided);
updateTicket();
return t;
}
public void expire() {
this.getTicket().expire();
updateTicket();
}
public boolean isRoot() {
return getTicket().isRoot();
}
public List<Authentication> getChainedAuthentications() {
return getTicket().getChainedAuthentications();
}
}
我们看到在这个类中增加了对ticket内部数据的一些操作。他们的具体实现方法都写在了TicketGrantingTicketImpl类中
public synchronized ServiceTicket grantServiceTicket(final String id,
final Service service, final ExpirationPolicy expirationPolicy,
final boolean credentialsProvided) {
final ServiceTicket serviceTicket = new ServiceTicketImpl(id, this,
service, this.getCountOfUses() == 0 || credentialsProvided,
expirationPolicy);
updateState();
final List<Authentication> authentications = getChainedAuthentications();
service.setPrincipal(authentications.get(authentications.size()-1).getPrincipal());
this.services.put(id, service);
return serviceTicket;
}
private void logOutOfServices() {
for (final Entry<String, Service> entry : this.services.entrySet()) {
if (!entry.getValue().logOutOfService(entry.getKey())) {
LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing...");
}
}
}
public boolean isRoot() {
return this.getGrantingTicket() == null;
}
public synchronized void expire() {
this.expired = true;
logOutOfServices();
}
public boolean isExpiredInternal() {
return this.expired;
}
public List<Authentication> getChainedAuthentications() {
final List<Authentication> list = new ArrayList<Authentication>();
if (this.getGrantingTicket() == null) {
list.add(this.getAuthentication());
return Collections.unmodifiableList(list);
}
list.add(this.getAuthentication());
list.addAll(this.getGrantingTicket().getChainedAuthentications());
return Collections.unmodifiableList(list);
}
ST代理类的设计也非常类似。
private static final class ServiceTicketDelegator extends TicketDelegator<ServiceTicket> implements ServiceTicket {
protected ServiceTicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, final ServiceTicket serviceTicket, final boolean callback) {
super(ticketRegistry, serviceTicket, callback);
}
public Service getService() {
return getTicket().getService();
}
public boolean isFromNewLogin() {
return getTicket().isFromNewLogin();
}
public boolean isValidFor(final Service service) {
final boolean b = this.getTicket().isValidFor(service);
updateTicket();
return b;
}
public TicketGrantingTicket grantTicketGrantingTicket(final String id, final Authentication authentication, final ExpirationPolicy expirationPolicy) {
final TicketGrantingTicket t = this.getTicket().grantTicketGrantingTicket(id, authentication, expirationPolicy);
updateTicket();
return t;
}
}
总结:cas的所有ticket都基于同一个借口,而ticket附加的一些具体行为都写分别写在了他们 实现类中,比如tgt与ST的互相获取(我们知道tgt对应着多个st)。
而这些ticket的基本行为,cas并没有直接把他们暴露出去。而是增加了一个代理类,这样其他类调用JpaTicketRegistry时在增加删除时无影响而在getticekt时,
获得到的ticket并不是源ticket,这个对象已经拥有了一些额外方法。使用时无需关心数据是如何联立的,删除某个ticket而又需要删除多少关联数据。这些人物都将
在代理类中进行调用。而在代码中,我们看到代理类的一些方法都调用了updateTicket方法,此方法就是更新ticket数据。这个方法是private的,说明获取的ticket不允许被
显示的update,这些工作只允许出现在某些特定的业务方法中,这也完全体现出了代理模式的优点。