CAS 原理和协议

基础模式

基础模式 SSO 访问流程主要有以下步骤:

1. 访问服务: SSO 客户端发送请求访问应用系统提供的服务资源。

2. 定向认证: SSO 客户端会重定向用户请求到 SSO 服务器。

3. 用户认证:用户身份认证。

4. 发放票据: SSO 服务器会产生一个随机的 Service Ticket 。

5. 验证票据: SSO 服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。

6. 传输用户信息: SSO 服务器验证票据通过后,传输用户认证结果信息给客户端。

下面是 CAS 最基本的协议过程:

CAS单点登录原理以及debug跟踪登录流程-LMLPHP

基础协议图

如 上图: CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护 Web 应用的受保护资源,过滤从客户端过来的每一个 Web 请求,同 时, CAS Client 会分析 HTTP 请求中是否包含请求 Service Ticket( ST 上图中的 Ticket) ,如果没有,则说明该用户是没有经过认证的;于是 CAS Client 会重定向用户请求到 CAS Server ( Step 2 ),并传递 Service (要访问的目的资源地址)。 Step 3 是用户认证过程,如果用户提供了正确的 Credentials , CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket ,并缓存以待将来验证,并且重定向用户到 Service 所在地址(附带刚才产生的 Service Ticket ) , 并为客户端浏览器设置一个 Ticket Granted Cookie ( TGC ) ; CAS Client 在拿到 Service 和新产生的 Ticket 过后,在 Step 5 和 Step6 中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。

在该协议中,所有与 CAS Server 的交互均采用 SSL 协议,以确保 ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向 的过程。但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的(使用 HttpsURLConnection )。

CAS 请求认证时序图如下:

CAS单点登录原理以及debug跟踪登录流程-LMLPHP

CAS 如何实现 SSO

当用户访问另一个应用的服务再次被重定向到 CAS Server 的时候, CAS Server 会主动获到这个 TGC cookie ,然后做下面的事情:

1) 如果 User 持有 TGC 且其还没失效,那么就走基础协议图的 Step4 ,达到了 SSO 的效果;

2) 如果 TGC 失效,那么用户还是要重新认证 ( 走基础协议图的 Step3) 。

以上是在网络上找到的相关描述,详细请参考:

http://www.open-open.com/lib/view/open1432381488005.html

但是光看文字描述还是不够清晰,不如Debug来看一下。

----------------------------------------------------------------------------------

前提:

有两个web应用

app1.testcas.com

app2.testcas.com

Cas认证中心

demo.testcas.com

第一步:访问目标应用app1

如果想要访问app1的网页

例如:app1.testcas.com/user/doWelcome

这时,该请求将会被事先配置好的CAS Filter所拦截

app1的web.xml配置如下:

<filter>
<filter-name>CAS Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://demo.testcas.com/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://app1.testcas.com</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
断点进入该类的doFilter方法

org.jasig.cas.client.authentication.AuthenticationFilter > doFilter
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;
final HttpSession session = request.getSession(false); // 该变量为判断用户是否已经登录的标记,在用户成功登录后会被设置
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION)
: null; // 判断是否登录过,如果已经登录过,进入if并且退出
if (assertion != null)
{
filterChain.doFilter(request, response);
return;
}
// 如果没有登录过,继续后续处理 // 构造访问的URL,如果该Url包含tikicet参数,则去除参数
final String serviceUrl = constructServiceUrl(request, response);
// 如果ticket存在,则获取URL后面的参数ticket
final String ticket = CommonUtils.safeGetParameter(request,
getArtifactParameterName());
// 研究中
final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request,
serviceUrl); // 如果ticket存在
if (CommonUtils.isNotBlank(ticket) || wasGatewayed)
{
filterChain.doFilter(request, response);
return;
} final String modifiedServiceUrl; log.debug("no ticket and no assertion found");
if (this.gateway)
{
log.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request,
serviceUrl);
}
else
{
modifiedServiceUrl = serviceUrl;
} if (log.isDebugEnabled())
{
log.debug("Constructed service url: " + modifiedServiceUrl);
} // 如果用户没有登录过,那么构造重定向的URL
final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
getServiceParameterName(),
modifiedServiceUrl,
this.renew,
this.gateway); if (log.isDebugEnabled())
{
log.debug("redirecting to \"" + urlToRedirectTo + "\"");
} // 重定向跳转到Cas认证中心
response.sendRedirect(urlToRedirectTo);
}

第二步:请求被重定向到CAS服务器端后

根据CAS_Server端的login-webflow.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"> <var name="credentials"
class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
<on-start>
<evaluate expression="initialFlowSetupAction" />
</on-start> <decision-state id="ticketGrantingTicketExistsCheck">
<if test="flowScope.ticketGrantingTicketId neq null" then="hasServiceCheck"
else="gatewayRequestCheck" />
</decision-state> <decision-state id="gatewayRequestCheck">
<if
test="externalContext.requestParameterMap['gateway'] neq '' &amp;&amp; externalContext.requestParameterMap['gateway'] neq null &amp;&amp; flowScope.service neq null"
then="gatewayServicesManagementCheck" else="generateLoginTicket" />
</decision-state> <decision-state id="hasServiceCheck">
<if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />
</decision-state> <decision-state id="renewRequestCheck">
<if
test="externalContext.requestParameterMap['renew'] neq '' &amp;&amp; externalContext.requestParameterMap['renew'] neq null"
then="generateLoginTicket" else="generateServiceTicket" />
</decision-state> <!-- The "warn" action makes the determination of whether to redirect directly
to the requested service or display the "confirmation" page to go back to
the server. -->
<decision-state id="warn">
<if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />
</decision-state> <!-- <action-state id="startAuthenticate"> <action bean="x509Check" /> <transition
on="success" to="sendTicketGrantingTicket" /> <transition on="warn" to="warn"
/> <transition on="error" to="generateLoginTicket" /> </action-state> --> <action-state id="generateLoginTicket">
<evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
<transition on="success" to="viewLoginForm" />
</action-state> <view-state id="viewLoginForm" view="casMyLoginView" model="credentials">
<binder>
<binding property="username" />
<binding property="password" />
<binding property="imgverifycode" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'credentials'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="imgverifycodeValidate">
<evaluate
expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state> <action-state id="imgverifycodeValidate">
<evaluate
expression="authenticationViaFormAction.validatorCode(flowRequestContext, flowScope.credentials, messageContext)" />
<transition on="error" to="generateLoginTicket" />
<transition on="success" to="realSubmit" />
</action-state> <action-state id="realSubmit">
<evaluate
expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="generateLoginTicket" />
</action-state> <action-state id="sendTicketGrantingTicket">
<evaluate expression="sendTicketGrantingTicketAction" />
<transition to="serviceCheck" />
</action-state> <decision-state id="serviceCheck">
<if test="flowScope.service neq null" then="generateServiceTicket"
else="viewGenericLoginSuccess" />
</decision-state> <action-state id="generateServiceTicket">
<evaluate expression="generateServiceTicketAction" />
<transition on="success" to="warn" />
<transition on="error" to="generateLoginTicket" />
<transition on="gateway" to="gatewayServicesManagementCheck" />
</action-state> <action-state id="gatewayServicesManagementCheck">
<evaluate expression="gatewayServicesManagementCheck" />
<transition on="success" to="redirect" />
</action-state> <action-state id="redirect">
<evaluate
expression="flowScope.service.getResponse(requestScope.serviceTicketId)"
result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
<transition to="postRedirectDecision" />
</action-state> <decision-state id="postRedirectDecision">
<if test="requestScope.response.responseType.name() eq 'POST'"
then="postView" else="redirectView" />
</decision-state> <!-- the "viewGenericLogin" is the end state for when a user attempts to
login without coming directly from a service. They have only initialized
their single-sign on session. -->
<end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" /> <!-- The "showWarningView" end state is the end state for when the user
has requested privacy settings (to be "warned") to be turned on. It delegates
to a view defines in default_views.properties that display the "Please click
here to go to the service." message. -->
<end-state id="showWarningView" view="casLoginConfirmView" /> <end-state id="postView" view="postResponseView">
<on-entry>
<set name="requestScope.parameters" value="requestScope.response.attributes" />
<set name="requestScope.originalUrl" value="flowScope.service.id" />
</on-entry>
</end-state> <!-- The "redirect" end state allows CAS to properly end the workflow while
still redirecting the user back to the service required. -->
<end-state id="redirectView" view="externalRedirect:${requestScope.response.url}" /> <end-state id="viewServiceErrorView" view="viewServiceErrorView" /> <end-state id="viewServiceSsoErrorView" view="viewServiceSsoErrorView" /> <global-transitions>
<transition to="viewServiceErrorView"
on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />
<transition to="viewServiceSsoErrorView"
on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException" />
<transition to="viewServiceErrorView"
on-exception="org.jasig.cas.services.UnauthorizedServiceException" />
</global-transitions>
</flow>

首先会进入initialFlowSetupAction

 protected Event doExecute(final RequestContext context) throws Exception {
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
if (!this.pathPopulated) {
final String contextPath = context.getExternalContext().getContextPath();
final String cookiePath = StringUtils.hasText(contextPath) ? contextPath + "/" : "/";
logger.info("Setting path for cookies to: "
+ cookiePath);
this.warnCookieGenerator.setCookiePath(cookiePath);
this.ticketGrantingTicketCookieGenerator.setCookiePath(cookiePath);
this.pathPopulated = true;
} // 获取客户端的名为CASTGC的cookie
context.getFlowScope().put(
"ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
context.getFlowScope().put(
"warnCookieValue",
Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request))); // 获取要访问的服务
final Service service = WebUtils.getService(this.argumentExtractors,
context); if (service != null && logger.isDebugEnabled()) {
logger.debug("Placing service in FlowScope: " + service.getId());
} context.getFlowScope().put("service", service); return result("success");
}

之后根据webflow流程,主要有两大分歧

如果TGC并且service存在,则发放ST(service ticket)并重定向回到客户端应用

如果首次访问,TGC不存在,则跳转到CAS-server的登录页面,如下(本登录页面是重新绘制,不是CAS原生登录页)

CAS单点登录原理以及debug跟踪登录流程-LMLPHP

因为我是首次登录,所以会跳转到该登录页进行认证。

第三步:用户认证

输入用户名、密码、验证码,点击登录

这时再来看login-webflow.xml

用户提交登录后,按流程依次是

1.authenticationViaFormAction.doBind

    <view-state id="viewLoginForm" view="casMyLoginView" model="credentials">
<binder>
<binding property="username" />
<binding property="password" />
<binding property="imgverifycode" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'credentials'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="imgverifycodeValidate">
<evaluate
expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state>

=>

2.imgverifycodeValidate(验证码处理为自定义的处理,不是原生逻辑)

    <action-state id="imgverifycodeValidate">
<evaluate
expression="authenticationViaFormAction.validatorCode(flowRequestContext, flowScope.credentials, messageContext)" />
<transition on="error" to="generateLoginTicket" />
<transition on="success" to="realSubmit" />
</action-state>

=>

3.realSubmit

<action-state id="realSubmit">
<evaluate
expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="generateLoginTicket" />
</action-state>

realSubmit中执行的是authenticationViaFormAction.submit

 public final String submit(final RequestContext context,
final Credentials credentials, final MessageContext messageContext)
throws Exception
{
// Validate login ticket
final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context);
final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context);
if (!authoritativeLoginTicket.equals(providedLoginTicket))
{
this.logger.warn("Invalid login ticket " + providedLoginTicket);
final String code = "INVALID_TICKET";
messageContext.addMessage(new MessageBuilder().error()
.code(code)
.arg(providedLoginTicket)
.defaultText(code)
.build());
return "error";
} // 获取TGT,首次登录的话应该是不存在的,所以直接跳过该分歧
final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
final Service service = WebUtils.getService(context);
if (StringUtils.hasText(context.getRequestParameters().get("renew"))
&& ticketGrantingTicketId != null && service != null)
{ try
{
final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId,
service,
credentials);
WebUtils.putServiceTicketInRequestScope(context,
serviceTicketId);
putWarnCookieIfRequestParameterPresent(context);
return "warn";
}
catch (final TicketException e)
{
if (e.getCause() != null
&& AuthenticationException.class.isAssignableFrom(e.getCause()
.getClass()))
{
populateErrorsInstance(e, messageContext);
return "error";
}
this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
if (logger.isDebugEnabled())
{
logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials",
e);
}
}
} try
{
// 首次登录时,用户输入信息验证成功后,创建一个新的TGT
WebUtils.putTicketGrantingTicketInRequestScope(context,
this.centralAuthenticationService.createTicketGrantingTicket(credentials));
putWarnCookieIfRequestParameterPresent(context);
return "success";
}
catch (final TicketException e)
{
// 如果用户输入信息验证不通过,会抛出异常,并在页面上显示
populateErrorsInstance(e, messageContext);
return "error";
}
}

=>

4.用户信息认证通过,并且创建了新的TGT后,缓存TGT,并且生成cookie,待后续把cookie写入客户端

    <action-state id="sendTicketGrantingTicket">
<evaluate expression="sendTicketGrantingTicketAction" />
<transition to="serviceCheck" />
</action-state>
sendTicketGrantingTicketAction.doExecute
    protected Event doExecute(final RequestContext context) {
final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
final String ticketGrantingTicketValueFromCookie = (String) context.getFlowScope().get("ticketGrantingTicketId"); if (ticketGrantingTicketId == null) {
return success();
} // 生成Cookie并且写入response,最终在客户端Cookie中保存了本TGT
this.ticketGrantingTicketCookieGenerator.addCookie(WebUtils.getHttpServletRequest(context), WebUtils
.getHttpServletResponse(context), ticketGrantingTicketId); if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) {
this.centralAuthenticationService
.destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie);
} return success();
}

=>

5. 然后验证是否存在Service,如果存在,生成ST,重定向用户到 Service 所在地址(附带该ST ) , 并为客户端浏览器设置一个 Ticket Granted Cookie ( TGC ) 

serviceCheck => generateServiceTicket => warn => redirect =>postRedirectDecision

第四步:拿着新产生的ST,到 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。

当cas Service重定向到客户端所在service时,该重定向请求同样会被客户端配置的过滤器所拦截,又进入了第一步处的AuthenticationFilter

但是由于本次请求已经带回了ST(service ticket),所以处理与首次有所不同。

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;
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 = CommonUtils.safeGetParameter(request,
getArtifactParameterName());
final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request,
serviceUrl); // 由于本次已经可以取到cas service返回的新的service ticket
if (CommonUtils.isNotBlank(ticket) || wasGatewayed)
{
// 所以直接进入本代码块,然后退出
filterChain.doFilter(request, response);
return;
}
// 不会再一次被重定向会cas 认证中心 final String modifiedServiceUrl; log.debug("no ticket and no assertion found");
if (this.gateway)
{
log.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request,
serviceUrl);
}
else
{
modifiedServiceUrl = serviceUrl;
} if (log.isDebugEnabled())
{
log.debug("Constructed service url: " + modifiedServiceUrl);
} // 如果用户没有登录过,那么构造重定向的URL
final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
getServiceParameterName(),
modifiedServiceUrl,
this.renew,
this.gateway); if (log.isDebugEnabled())
{
log.debug("redirecting to \"" + urlToRedirectTo + "\"");
} // 重定向跳转到Cas认证中心
response.sendRedirect(urlToRedirectTo);
}

之后,又会被web.xml中的CAS Validation Filter(Cas20ProxyReceivingTicketValidationFilter)所拦截

该拦截器用来与CAS Server 进行身份核实,以确保 Service Ticket 的合法性

由于 Cas20ProxyReceivingTicketValidationFilter 没有重写doFilter方法,所以会进入父类AbstractTicketValidationFilter的doFilter方法

AbstractTicketValidationFilter.doFilter

 public final void doFilter(final ServletRequest servletRequest,
final ServletResponse servletResponse, final FilterChain filterChain)
throws IOException, ServletException
{ if (!preFilter(servletRequest, servletResponse, filterChain))
{
return;
} final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final String ticket = CommonUtils.safeGetParameter(request,
getArtifactParameterName()); if (CommonUtils.isNotBlank(ticket))
{
if (log.isDebugEnabled())
{
log.debug("Attempting to validate ticket: " + ticket);
} try
{
// 构造验证URL,向cas server发起验证请求
final Assertion assertion = this.ticketValidator.validate(ticket,
constructServiceUrl(request, response));
if (log.isDebugEnabled())
{
log.debug("Successfully authenticated user: "
+ assertion.getPrincipal().getName());
} // 如果验证成功,设置assertion,当再一次发起访问请求时,如果发现assertion已经被设置,所以已经通过验证,不过再次重定向会cas认证中心
request.setAttribute(CONST_CAS_ASSERTION, assertion); if (this.useSession)
{
request.getSession().setAttribute(CONST_CAS_ASSERTION,
assertion);
}
onSuccessfulValidation(request, response, assertion); if (this.redirectAfterValidation)
{
log.debug("Redirecting after successful ticket validation.");
response.sendRedirect(constructServiceUrl(request, response));
return;
}
}
catch (final TicketValidationException e)
{
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
log.warn(e, e); onFailedValidation(request, response); if (this.exceptionOnValidationFailure)
{
throw new ServletException(e);
} return;
}
} filterChain.doFilter(request, response); }
this.ticketValidator.validate(..) 代码如下
 public Assertion validate(final String ticket, final String service)
throws TicketValidationException
{ // 生成验证URL,如果你debug会发现,此处会构造一个类似以下的URL,访问的是cas server的serviceValidate方法
// https://demo.testcas.com/cas/serviceValidate?ticket=ST-31-cioaDNxSpUWIgeYEn4yK-cas&service=http%3A%2F%2Fapp1.testcas.com%2Fb2c-haohai-server%2Fuser%2FcasLogin
final String validationUrl = constructValidationUrl(ticket, service);
if (log.isDebugEnabled())
{
log.debug("Constructing validation url: " + validationUrl);
} try
{
log.debug("Retrieving response from server.");
// 得到cas service响应,验证成功或者失败
final String serverResponse = retrieveResponseFromServer(new URL(
validationUrl), ticket); if (serverResponse == null)
{
throw new TicketValidationException(
"The CAS server returned no response.");
} if (log.isDebugEnabled())
{
log.debug("Server response: " + serverResponse);
} return parseResponseFromServer(serverResponse);
}
catch (final MalformedURLException e)
{
throw new TicketValidationException(e);
}
}
可以看一下,cas server侧的serverValidate的具体实现
在cas server的cas-servlet.xml中,可以看到如下配置:
    <bean
id="handlerMappingC"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property
name="mappings">
<props>
<prop
key="/logout">
logoutController
</prop>
<prop
key="/serviceValidate">
serviceValidateController
</prop>
...
指向serviceValidateController

ServiceValidateController.handleRequestInternal(...)
 protected final ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
final WebApplicationService service = this.argumentExtractor.extractService(request);
final String serviceTicketId = service != null ? service.getArtifactId() : null; if (service == null || serviceTicketId == null) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Could not process request; Service: %s, Service Ticket Id: %s", service, serviceTicketId));
}
return generateErrorView("INVALID_REQUEST", "INVALID_REQUEST", null);
} try {
final Credentials serviceCredentials = getServiceCredentialsFromRequest(request);
String proxyGrantingTicketId = null; // XXX should be able to validate AND THEN use
if (serviceCredentials != null) {
try {
proxyGrantingTicketId = this.centralAuthenticationService
.delegateTicketGrantingTicket(serviceTicketId,
serviceCredentials);
} catch (final TicketException e) {
logger.error("TicketException generating ticket for: "
+ serviceCredentials, e);
}
} final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service); final ValidationSpecification validationSpecification = this.getCommandClass();
final ServletRequestDataBinder binder = new ServletRequestDataBinder(validationSpecification, "validationSpecification");
initBinder(request, binder);
binder.bind(request); if (!validationSpecification.isSatisfiedBy(assertion)) {
if (logger.isDebugEnabled()) {
logger.debug("ServiceTicket [" + serviceTicketId + "] does not satisfy validation specification.");
}
return generateErrorView("INVALID_TICKET", "INVALID_TICKET_SPEC", null);
} onSuccessfulValidation(serviceTicketId, assertion); final ModelAndView success = new ModelAndView(this.successView);
success.addObject(MODEL_ASSERTION, assertion); if (serviceCredentials != null && proxyGrantingTicketId != null) {
final String proxyIou = this.proxyHandler.handle(serviceCredentials, proxyGrantingTicketId);
success.addObject(MODEL_PROXY_GRANTING_TICKET_IOU, proxyIou);
} if (logger.isDebugEnabled()) {
logger.debug(String.format("Successfully validated service ticket: %s", serviceTicketId));
} return success;
} catch (final TicketValidationException e) {
return generateErrorView(e.getCode(), e.getCode(), new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()});
} catch (final TicketException te) {
return generateErrorView(te.getCode(), te.getCode(),
new Object[] {serviceTicketId});
} catch (final UnauthorizedServiceException e) {
return generateErrorView(e.getMessage(), e.getMessage(), null);
}
}
验证成功后,就可以正常访问了。
04-26 22:43