CAS流程简析 服务端校验Ticket
相关阅读
简介
用户访问客户端的请求若携带Ticket信息,经过客户端配置的过滤器Cas30ProxyReceivingTicketValidationFilter
时,该过滤器会将请求中携带的Ticket
信息发送到服务端进行校验,若校验通过,才返回鉴权结果;
简析
Cas30ProxyReceivingTicketValidationFilter
发送到服务端的请求路径为:/p3/serviceValidate
,对应服务端的处理器为V3ServiceValidateController
,核心代码如下:
@Component("v3ServiceValidateController")
@Controller
public class V3ServiceValidateController extends AbstractServiceValidateController {
/**
* Handle model and view.
*
* @param request the request
* @param response the response
* @return the model and view
* @throws Exception the exception
*/
@RequestMapping(path="/p3/serviceValidate", method = RequestMethod.GET)
protected ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
return super.handleRequestInternal(request, response);
}
@Override
@Autowired
public void setValidationSpecificationClass(@Value("org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification")
final Class<?> validationSpecificationClass) {
super.setValidationSpecificationClass(validationSpecificationClass);
}
@Override
@Autowired
public void setFailureView(@Value("cas3ServiceFailureView") final String failureView) {
super.setFailureView(failureView);
}
@Override
@Autowired
public void setSuccessView(@Value("cas3ServiceSuccessView") final String successView) {
super.setSuccessView(successView);
}
@Override
@Autowired
public void setProxyHandler(@Qualifier("proxy20Handler") final ProxyHandler proxyHandler) {
super.setProxyHandler(proxyHandler);
}
}
V3ServiceValidateController
校验Ticket
方法来源于父类AbstractServiceValidateController
的handleRequestInternal
方法,代码如下:
protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
// 从请求中获取Service信息
final WebApplicationService service = this.argumentExtractor.extractService(request);
// 获取ST ID
final String serviceTicketId = service != null ? service.getArtifactId() : null;
// 校验Service和ST ID
if (service == null || serviceTicketId == null) {
logger.debug("Could not identify service and/or service ticket for service: [{}]", service);
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_REQUEST,
CasProtocolConstants.ERROR_CODE_INVALID_REQUEST, null, request, service);
}
try {
// 获取pgtUrl的鉴权结果
// 代理模式涉及,本例不涉及
TicketGrantingTicket proxyGrantingTicketId = null;
final Credential serviceCredential = getServiceCredentialsFromRequest(service, request);
if (serviceCredential != null) {
proxyGrantingTicketId = handleProxyGrantingTicketDelivery(serviceTicketId, serviceCredential);
if (proxyGrantingTicketId == null) {
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
new Object[]{serviceCredential.getId()}, request, service);
}
}
// 校验ST ID
final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);
// 校验认证结果
if (!validateAssertion(request, serviceTicketId, assertion)) {
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_TICKET,
CasProtocolConstants.ERROR_CODE_INVALID_TICKET, null, request, service);
}
// 获取proxyIou
// 代理模式涉及,本例不涉及
String proxyIou = null;
if (serviceCredential != null && this.proxyHandler.canHandle(serviceCredential)) {
proxyIou = this.proxyHandler.handle(serviceCredential, proxyGrantingTicketId);
if (StringUtils.isEmpty(proxyIou)) {
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
new Object[] {serviceCredential.getId()}, request, service);
}
}
// 校验成功时处理
onSuccessfulValidation(serviceTicketId, assertion);
logger.debug("Successfully validated service ticket {} for service [{}]", serviceTicketId, service.getId());
// 创建成功视图
return generateSuccessView(assertion, proxyIou, service, proxyGrantingTicketId);
} catch (final AbstractTicketValidationException e) {
final String code = e.getCode();
return generateErrorView(code, code,
new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()}, request, service);
} catch (final AbstractTicketException te) {
return generateErrorView(te.getCode(), te.getCode(),
new Object[] {serviceTicketId}, request, service);
} catch (final UnauthorizedProxyingException e) {
return generateErrorView(e.getMessage(), e.getMessage(), new Object[] {service.getId()}, request, service);
} catch (final UnauthorizedServiceException e) {
return generateErrorView(e.getMessage(), e.getMessage(), null, request, service);
}
}
主要逻辑如下:
- 获取并校验
Service
信息和ST ID; - 获取pgtUrl;
- 校验ST ID;
- 校验认证结果;
- 获取proxyIou;
- 创建视图;
1 获取并校验Service信息和ST ID
首先分析从request中如何获取Service
和ST ID,代码如下:
@Qualifier("defaultArgumentExtractor")
private ArgumentExtractor argumentExtractor;
final WebApplicationService service = this.argumentExtractor.extractService(request);
final String serviceTicketId = service != null ? service.getArtifactId() : null;
defaultArgumentExtractor
对应的是DefaultArgumentExtractor
,其extractService
继承自父类AbstractArgumentExtractor
,AbstractArgumentExtractor
实现了extractService
的算法模板,提供算法细节extractServiceInternal
由子类实现实现,还提供了serviceFactoryList
供子类实现使用,其代码如下:
public final WebApplicationService extractService(final HttpServletRequest request) {
// 从request中抽取Service信息
final WebApplicationService service = extractServiceInternal(request);
// log
if (service == null) {
logger.debug("Extractor did not generate service.");
} else {
logger.debug("Extractor generated service for: {}", service.getId());
}
return service;
}
protected abstract WebApplicationService extractServiceInternal(HttpServletRequest request);
@Resource(name="serviceFactoryList")
protected List<ServiceFactory<? extends WebApplicationService>> serviceFactoryList;
protected final List<ServiceFactory<? extends WebApplicationService>> getServiceFactories() {
return serviceFactoryList;
}
serviceFactoryList
的配置如下:
<!-- services-context.xml -->
<util:list id="serviceFactoryList" value-type="org.jasig.cas.authentication.principal.ServiceFactory">
<ref bean="webApplicationServiceFactory" />
</util:list>
DefaultArgumentExtractor
实现了算法细节extractServiceInternal
,代码如下:
public WebApplicationService extractServiceInternal(final HttpServletRequest request) {
for (final ServiceFactory<? extends WebApplicationService> factory : getServiceFactories()) {
final WebApplicationService service = factory.createService(request);
if (service != null) {
// 创建成功则直接返回
logger.debug("Created {} based on {}", service, factory);
return service;
}
}
logger.debug("No service could be extracted based on the given request");
return null;
}
webApplicationServiceFactory
对应的是WebApplicationServiceFactory
,其createService
方法代码如下:
public WebApplicationService createService(final HttpServletRequest request) {
final String targetService = request.getParameter(CasProtocolConstants.PARAMETER_TARGET_SERVICE);
final String service = request.getParameter(CasProtocolConstants.PARAMETER_SERVICE);
final String serviceAttribute = (String) request.getAttribute(CasProtocolConstants.PARAMETER_SERVICE);
final String method = request.getParameter(CasProtocolConstants.PARAMETER_METHOD);
final String format = request.getParameter(CasProtocolConstants.PARAMETER_FORMAT);
final String serviceToUse;
if (StringUtils.isNotBlank(targetService)) {
// 优先使用targetService
serviceToUse = targetService;
} else if (StringUtils.isNotBlank(service)) {
// 其次使用请求参数中的service
serviceToUse = service;
} else {
// 最后使用请求属性中的service
serviceToUse = serviceAttribute;
}
// 校验service信息
if (StringUtils.isBlank(serviceToUse)) {
return null;
}
// 去除jsession信息
final String id = AbstractServiceFactory.cleanupUrl(serviceToUse);
// 获取请求参数中的ticket信息,并将其作为Service的artifactId
final String artifactId = request.getParameter(CasProtocolConstants.PARAMETER_TICKET);
final Response.ResponseType type = HttpMethod.POST.name().equalsIgnoreCase(method) ? Response.ResponseType.POST
: Response.ResponseType.REDIRECT;
// 创建SimpleWebApplicationServiceImpl
final SimpleWebApplicationServiceImpl webApplicationService =
new SimpleWebApplicationServiceImpl(id, serviceToUse,
artifactId, new WebApplicationServiceResponseBuilder(type));
try {
if (StringUtils.isNotBlank(format)) {
// 若请求参数中存在format信息,则设置该属性
final ValidationResponseType formatType = ValidationResponseType.valueOf(format.toUpperCase());
webApplicationService.setFormat(formatType);
}
} catch (final Exception e) {
logger.error("Format specified in the request [{}] is not recognized", format);
return null;
}
return webApplicationService;
}
WebApplicationServiceFactory
创建的Service
为SimpleWebApplicationServiceImpl
,支持单点登出;
public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicationService
public abstract class AbstractWebApplicationService implements SingleLogoutService
public interface SingleLogoutService extends WebApplicationService
public interface WebApplicationService extends Service
2 获取pgtUrl
根据请求中的pgtUrl信息获取对应的鉴权结果,代码如下:
protected Credential getServiceCredentialsFromRequest(final WebApplicationService service, final HttpServletRequest request) {
// 获取请求中的"pgtUrl"参数
final String pgtUrl = request.getParameter(CasProtocolConstants.PARAMETER_PROXY_CALLBACK_URL);
if (StringUtils.hasText(pgtUrl)) {
// pgtUrl参数存在
try {
// 获取对应的已注册Service信息
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
// 校验已注册Service属性
verifyRegisteredServiceProperties(registeredService, service);
return new HttpBasedServiceCredential(new URL(pgtUrl), registeredService);
} catch (final Exception e) {
logger.error("Error constructing pgtUrl", e);
}
}
return null;
}
代理模式涉及该处理,本例不涉及;
3 校验ST ID
从request请求中获取到ST ID,需要对其进行校验,代码如下:
final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);
@Qualifier("centralAuthenticationService")
private CentralAuthenticationService centralAuthenticationService;
centralAuthenticationService
对应的是CentralAuthenticationServiceImpl
,其validateServiceTicket
方法代码如下:
public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws AbstractTicketException {
// 根据Service信息获取已注册Service信息
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
// 校验已注册Service属性
verifyRegisteredServiceProperties(registeredService, service);
// 根据ST ID获取ST
final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
// 校验ST
if (serviceTicket == null) {
logger.info("Service ticket [{}] does not exist.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
try {
synchronized (serviceTicket) {
// ST是否过期
if (serviceTicket.isExpired()) {
logger.info("ServiceTicket [{}] has expired.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
// ST是否支持当前Service
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());
}
}
// 获取ST对应的TGT
final TicketGrantingTicket root = serviceTicket.getGrantingTicket().getRoot();
// 获取鉴权结果
final Authentication authentication = getAuthenticationSatisfiedByPolicy(
root, new ServiceContext(serviceTicket.getService(), registeredService));
// 获取Principal
final Principal principal = authentication.getPrincipal();
final RegisteredServiceAttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy();
logger.debug("Attribute policy [{}] is associated with service [{}]", attributePolicy, registeredService);
@SuppressWarnings("unchecked")
final Map<String, Object> 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());
// 发布ST校验成功事件
doPublishEvent(new CasServiceTicketValidatedEvent(this, serviceTicket, assertion));
// 返回认证结果
return assertion;
} finally {
if (serviceTicket.isExpired()) {
this.ticketRegistry.deleteTicket(serviceTicketId);
}
}
}
主要逻辑如下:
- 根据Service信息获取已注册Service信息并校验;
- 根据ST ID获取ST;
- 校验ST;
- 创建认证结果;
3.1 根据Service信息获取已注册Service信息并校验
代码如下:
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
private ServicesManager servicesManager;
@Autowired
public void setServicesManager(@Qualifier("servicesManager") final ServicesManager servicesManager) {
this.servicesManager = servicesManager;
}
servicesManager
对应的是DefaultServicesManagerImpl
,findServiceBy
方法的代码如下:
public RegisteredService findServiceBy(final Service service) {
final Collection<RegisteredService> c = convertToTreeSet();
// 遍历注册的Service
for (final RegisteredService r : c) {
if (r.matches(service)) {
// 若匹配则返回该注册的Service
return r;
}
}
return null;
}
public TreeSet<RegisteredService> convertToTreeSet() {
return new TreeSet<>(this.services.values());
}
private ConcurrentHashMap<Long, RegisteredService> services = new ConcurrentHashMap<>();
public void load() {
final ConcurrentHashMap<Long, RegisteredService> localServices =
new ConcurrentHashMap<>();
// 借助this.serviceRegistryDao加载注册Service信息
for (final RegisteredService r : this.serviceRegistryDao.load()) {
LOGGER.debug("Adding registered service {}", r.getServiceId());
localServices.put(r.getId(), r);
}
this.services = localServices;
LOGGER.info("Loaded {} services from {}.", this.services.size(),
this.serviceRegistryDao);
}
public DefaultServicesManagerImpl(@Qualifier("serviceRegistryDao") final ServiceRegistryDao serviceRegistryDao) {
this.serviceRegistryDao = serviceRegistryDao;
load();
}
ServiceRegistryDao
接口有多种实现,可根据实际需求,在配置文件中自行配置该接口的实现;以InMemoryServiceRegistryDaoImpl
为例,分析load
方法实现,代码如下:
public List<RegisteredService> load() {
return this.registeredServices;
}
@PostConstruct
public void afterPropertiesSet() {
final String[] aliases =
this.applicationContext.getAutowireCapableBeanFactory().getAliases("inMemoryServiceRegistryDao");
// 如果配置了"inMemoryServiceRegistryDao"
if (aliases.length > 0) {
LOGGER.debug("{} is used as the active service registry dao", this.getClass().getSimpleName());
try {
// 从IOC容器中找到"inMemoryRegisteredServices"的配置
final List<RegisteredService> list = (List<RegisteredService>)
this.applicationContext.getBean("inMemoryRegisteredServices", List.class);
if (list != null) {
LOGGER.debug("Loaded {} services from the application context for {}",
list.size(),
this.getClass().getSimpleName());
this.registeredServices = list;
}
} catch (final Exception e) {
LOGGER.debug("No registered services are defined for {}", this.getClass().getSimpleName());
}
}
}
inMemoryRegisteredServices
配置信息举例如下:
<util:list id="inMemoryRegisteredServices">
<bean class="org.jasig.cas.services.RegexRegisteredService"
p:id="0" p:name="HTTP and IMAP" p:description="Allows HTTP(S) and IMAP(S) protocols"
p:serviceId="^(https?|imaps?)://.*" p:evaluationOrder="10000001" >
<property name="attributeReleasePolicy">
<bean class="org.jasig.cas.services.ReturnAllAttributeReleasePolicy" />
</property>
</bean>
</util:list>
常用的RegisteredService
接口的实现类为RegexRegisteredService
,支持正则表达式,其match
方法实现代码如下:
public boolean matches(final Service service) {
if (this.servicePattern == null) {
this.servicePattern = RegexUtils.createPattern(this.serviceId);
}
// 根据serviceId的正则规则进行匹配
return service != null && this.servicePattern != null
&& this.servicePattern.matcher(service.getId()).matches();
}
3.2 根据ST ID获取ST
代码如下:
final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
@Resource(name="ticketRegistry")
protected TicketRegistry ticketRegistry;
TicketRegistry
由用户配置,可以看下DefaultTicketRegistry
的实现,getTicket
方法由AbstractTicketRegistry
实现了算法模板,代码如下:
public final <T extends Ticket> T getTicket(final String ticketId, final Class<? extends Ticket> clazz) {
Assert.notNull(clazz, "clazz cannot be null");
final Ticket ticket = this.getTicket(ticketId);
if (ticket == null) {
return null;
}
if (!clazz.isAssignableFrom(ticket.getClass())) {
throw new ClassCastException("Ticket [" + ticket.getId()
+ " is of type " + ticket.getClass()
+ " when we were expecting " + clazz);
}
return (T) ticket;
}
DefaultTicketRegistry
使用内存中的Map存储Ticket
信息,其getTicket
方法的代码如下:
public Ticket getTicket(final String ticketId) {
if (ticketId == null) {
return null;
}
logger.debug("Attempting to retrieve ticket [{}]", ticketId);
final Ticket ticket = this.cache.get(ticketId);
if (ticket != null) {
logger.debug("Ticket [{}] found in registry.", ticketId);
}
return ticket;
}
3.3 校验ST
3.3.1 ST是否过期
ServiceTicketImpl
的isExpired
方法继承自父类AbstractTicket
,AbstractTicket
实现了isExpired
的算法模板,代码如下:
public final boolean isExpired() {
final TicketGrantingTicket tgt = getGrantingTicket();
return this.expirationPolicy.isExpired(this)
|| (tgt != null && tgt.isExpired())
|| isExpiredInternal();
}
public final TicketGrantingTicket getGrantingTicket() {
return this.ticketGrantingTicket;
}
protected boolean isExpiredInternal() {
return false;
}
this.expirationPolicy
属性由DefaultServiceTicketFactory
创建ServiceTicketImpl
时传入,代码如下:
final ServiceTicket serviceTicket = ticketGrantingTicket.grantServiceTicket(
ticketId,
service,
this.serviceTicketExpirationPolicy,
credentialsProvided,
this.onlyTrackMostRecentSession);
@Resource(name="serviceTicketExpirationPolicy")
protected ExpirationPolicy serviceTicketExpirationPolicy;
serviceTicketExpirationPolicy
可由用户自行配置,默认配置如下:
<!-- deployerConfigContext.xml -->
<alias name="ticketGrantingTicketExpirationPolicy" alias="grantingTicketExpirationPolicy" />
<alias name="multiTimeUseOrTimeoutExpirationPolicy" alias="serviceTicketExpirationPolicy" />
multiTimeUseOrTimeoutExpirationPolicy
对应的是MultiTimeUseOrTimeoutExpirationPolicy
,其isExpired
方法代码如下:
// 默认超时时间为10s
@Value("#{${st.timeToKillInSeconds:10}*1000L}")
private final long timeToKillInMilliSeconds;
// 默认为1
@Value("${st.numberOfUses:1}")
private final int numberOfUses;
public boolean isExpired(final TicketState ticketState) {
if (ticketState == null) {
LOGGER.debug("Ticket state is null for {}", this.getClass().getSimpleName());
return true;
}
// 校验ST的使用数
final long countUses = ticketState.getCountOfUses();
if (countUses >= this.numberOfUses) {
LOGGER.debug("Ticket usage count {} is greater than or equal to {}", countUses, this.numberOfUses);
return true;
}
final long systemTime = System.currentTimeMillis();
final long lastTimeUsed = ticketState.getLastTimeUsed();
final long difference = systemTime - lastTimeUsed;
// 校验ST的超时时间
if (difference >= this.timeToKillInMilliSeconds) {
LOGGER.debug("Ticket has expired because the difference between current time [{}] "
+ "and ticket time [{}] is greater than or equal to [{}]", systemTime, lastTimeUsed,
this.timeToKillInMilliSeconds);
return true;
}
return false;
}
st.timeToKillInSeconds
和st.numberOfUses
可在cas.properties
文件中配置;
3.3.2 ST是否支持当前Service
代码如下:
public boolean isValidFor(final Service serviceToValidate) {
// 更新ST的访问记录
updateState();
//
return serviceToValidate.matches(this.service);
}
// AbstractWebApplicationService.java
public boolean matches(final Service service) {
try {
final String thisUrl = URLDecoder.decode(this.id, "UTF-8");
final String serviceUrl = URLDecoder.decode(service.getId(), "UTF-8");
logger.trace("Decoded urls and comparing [{}] with [{}]", thisUrl, serviceUrl);
return thisUrl.equalsIgnoreCase(serviceUrl);
} catch (final Exception e) {
logger.error(e.getMessage(), e);
}
return false;
}
3.4 创建认证结果
ST校验通过后,根据ST找到对应的TGT,从而找到对应的鉴权结果,然后将创建认证结果;
4 校验认证结果
代码如下:
private boolean validateAssertion(final HttpServletRequest request, final String serviceTicketId, final Assertion assertion) {
final ValidationSpecification validationSpecification = this.getCommandClass();
final ServletRequestDataBinder binder = new ServletRequestDataBinder(validationSpecification, "validationSpecification");
initBinder(request, binder);
binder.bind(request);
// 是否满足特定校验要求
if (!validationSpecification.isSatisfiedBy(assertion)) {
logger.debug("Service ticket [{}] does not satisfy validation specification.", serviceTicketId);
return false;
}
return true;
}
private ValidationSpecification getCommandClass() {
try {
return (ValidationSpecification) this.validationSpecificationClass.newInstance();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
private Class<?> validationSpecificationClass = Cas20ProtocolValidationSpecification.class;
默认使用的是Cas20ProtocolValidationSpecification
,其isSatisfiedBy
方法继承自父类AbstractCasProtocolValidationSpecification
,AbstractCasProtocolValidationSpecification
实现了isSatisfiedBy
的算法模板,并提供算法细节isSatisfiedByInternal
由子类实现,代码如下:
public final boolean isSatisfiedBy(final Assertion assertion) {
return isSatisfiedByInternal(assertion)
&& (!this.renew || assertion.isFromNewLogin());
}
protected abstract boolean isSatisfiedByInternal(Assertion assertion);
Cas20ProtocolValidationSpecification
实现了算法细节isSatisfiedByInternal
,代码如下:
protected boolean isSatisfiedByInternal(final Assertion assertion) {
return true;
}
5 获取proxyIou
本例不涉及;
6 创建视图
代码如下:
private ModelAndView generateSuccessView(final Assertion assertion, final String proxyIou,
final WebApplicationService service,
final TicketGrantingTicket proxyGrantingTicket) {
final ModelAndView modelAndView = getModelAndView(true, service);
modelAndView.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_ASSERTION, assertion);
modelAndView.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_SERVICE, service);
modelAndView.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET_IOU, proxyIou);
if (proxyGrantingTicket != null) {
modelAndView.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, proxyGrantingTicket.getId());
}
final Map<String, ?> augmentedModelObjects = augmentSuccessViewModelObjects(assertion);
if (augmentedModelObjects != null) {
modelAndView.addAllObjects(augmentedModelObjects);
}
return modelAndView;
}
private ModelAndView getModelAndView(final boolean isSuccess, final WebApplicationService service) {
if (service != null){
if (service.getFormat() == ValidationResponseType.JSON) {
return new ModelAndView(DEFAULT_SERVICE_VIEW_NAME_JSON);
}
}
return new ModelAndView(isSuccess ? this.successView : this.failureView);
}
this.successView
和this.failureView
被V3ServiceValidateController
重写,代码如下:
public void setFailureView(@Value("cas3ServiceFailureView") final String failureView) {
super.setFailureView(failureView);
}
public void setSuccessView(@Value("cas3ServiceSuccessView") final String successView) {
super.setSuccessView(successView);
}
6.1 成功视图
cas3ServiceSuccessView
的配置如下:
@Component("cas3ServiceSuccessView")
public static class Success extends Cas30ResponseView {
/**
* Instantiates a new Success.
* @param view the view
*/
@Autowired
public Success(@Qualifier("cas3JstlSuccessView")
final AbstractUrlBasedView view) {
super(view);
super.setSuccessResponse(true);
}
}
cas3JstlSuccessView
的配置如下:
<!-- protocolViewsConfiguration.xml -->
<bean id="cas3JstlSuccessView" class="org.springframework.web.servlet.view.JstlView"
c:url="/WEB-INF/view/jsp/protocol/3.0/casServiceValidationSuccess.jsp" />
casServiceValidationSuccess.jsp
文件的内容如下:
<%@ page session="false" contentType="application/xml; charset=UTF-8" %>
<%@ page import="java.util.*, java.util.Map.Entry" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>${fn:escapeXml(principal.id)}</cas:user>
<c:if test="${not empty pgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<c:if test="${fn:length(chainedAuthentications) > 0}">
<cas:proxies>
<c:forEach var="proxy" items="${chainedAuthentications}" varStatus="loopStatus" begin="0"
end="${fn:length(chainedAuthentications)}" step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
</cas:proxies>
</c:if>
<c:if test="${fn:length(attributes) > 0}">
<cas:attributes>
<c:forEach var="attr"
items="${attributes}"
varStatus="loopStatus" begin="0"
end="${fn:length(attributes)}"
step="1">
<c:forEach var="attrval" items="${attr.value}">
<cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attrval)}</cas:${fn:escapeXml(attr.key)}>
</c:forEach>
</c:forEach>
</cas:attributes>
</c:if>
</cas:authenticationSuccess>
</cas:serviceResponse>
本例中,服务端只会将principal.id信息返回给客户端;
6.2 失败视图
cas3ServiceFailureView
的配置如下:
<!-- protocolViewsConfiguration.xml -->
<bean id="cas3ServiceFailureView" class="org.springframework.web.servlet.view.JstlView"
c:url="/WEB-INF/view/jsp/protocol/3.0/casServiceValidationFailure.jsp" />
casServiceValidationFailure.jsp
文件的内容如下:
<%@ page session="false" contentType="application/xml; charset=UTF-8" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationFailure code='${code}'>
${fn:escapeXml(description)}
</cas:authenticationFailure>
</cas:serviceResponse>
服务端将错误码和错误信息返回给客户端;
至此,服务端校验Ticket
流程结束。