CAS单点登录开源框架解读(九)--CAS单点登录客户端认证之服务端验证票据
返回认证信息
服务端如何返回⽤户认证信息
上⼀章节中我们已经知道是通过http请求去到CAS服务端获取信息,根据CAS单点登录客户端的请求地址/serviceValidate,我们再CAS 单点登录服务端上通过Springmvc根据url⾥的/serviceValidate,匹配到@RequestMapping(path="/serviceValidate")
1. ticket校验开始:serviceValidate
/serviceValidate路径所对应的java类为ServiceValidateController.java,在cas-server-webapp-validation模块下的org.jasig.cas.web 包中。
*ServiceValidateController.java*
@Component("serviceValidateController")
@Controller
public class ServiceValidateController 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="/serviceValidate", method = RequestMethod.GET)
@Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request,final HttpServletResponse response)
throws Exception {
//交给⽗类去处理
return super.handleRequestInternal(request, response);
}
调⽤⽗类的handleRequestInternal()的⽅法。
2. 调⽤⽗类handleRequestInternal()⽅法
AbstractServiceValidateController.java类在cas-server-webapp-validation模块下的org.jasig.cas.web包中。
*AbstractServiceValidateController.java*
@Component("serviceValidateController")
public abstract class AbstractServiceValidateController extends AbstractDelegateController {
@Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request,final HttpServletResponse response)
throws Exception {
//CAS单点登录服务端根据请求获取到service
final WebApplicationService service =actService(request);
//根据服务端获取的service取到serviceId
final String serviceTicketId = service != null ? ArtifactId(): null;
//如果service为空,或者serviceId为空,返回错误信息给CAS单点登录客户端
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{
TicketGrantingTicket proxyGrantingTicketId = null;
//获取pgturl所对应的认证信息(代理模式下),此时为空
final Credential serviceCredential =getServiceCredentialsFromRequest(service, request);
if(serviceCredential != null){
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[]{Id()}, request, service);
}
}
//通过认证中⼼校验ticket,并返回认证信息
final Assertion assertion =alAuthenticationService.validateServiceTicket(serviceTicketId, service);
//根据认证⽤户信息和service,如果校验失败返回ticket校验失效信息到CAS单点登录客户端
if(!validateAssertion(request, serviceTicketId, assertion)){
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_TICKET,
CasProtocolConstants.ERROR_CODE_INVALID_TICKET, null, request, service);
}
//代理模式下的pgtIOU,此时⾮代理模式下获取为空
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[]{Id()}, request, service);
}
}
onSuccessfulValidation(serviceTicketId, assertion);
logger.debug("Successfully validated service ticket {} for service [{}]", serviceTicketId, Id());
//返回到CAS服务端成功页⾯,同时把相关参数信息返回给CAS单点登录客户端
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(), Id()}, request, service);
}catch(final AbstractTicketException te){
return Code(), te.getCode(),
new Object[]{serviceTicketId}, request, service);
}catch(final UnauthorizedProxyingException e){
return Message(), e.getMessage(),new Object[]{Id()}, request, service);
}catch(final UnauthorizedServiceException e){
return Message(), e.getMessage(), null, request, service);
}
}
…………
注意校验成功和失败时所构建的jsp页⾯是不⼀样的,当成功时请求跳转的页⾯为casServiceValidationSuccess.jsp;⽽当失败时,请求的跳转页⾯为casServiceValidationFailure.jsp。
调⽤中⼼认证服务器校验服务ticket票据:centralAuthenticationService.validateServiceTicket。
3. 调⽤认证中⼼centralAuthenticationService
CentralAuthenticationServiceImpl.java此类在cas-server-core模块下的org.jasig.cas包中。CentralAuthenticationServiceImpl.java
@Component("centralAuthenticationService")
public final class CentralAuthenticationServiceImpl extends AbstractCentralAuthenticationService {
@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 {
public Assertion validateServiceTicket(final String serviceTicketId,final Service service)throws AbstractTicketException { //根据service获取到cas服务端维护的service地址
final RegisteredService registeredService =this.servicesManager.findServiceBy(service);
//校验获取到的注册service是否满⾜CAS单点登录服务端的service规则
verifyRegisteredServiceProperties(registeredService, service);
//根据serviceId获取到serviceTicket
final ServiceTicket serviceTicket =Ticket(serviceTicketId, ServiceTicket.class);
if(serviceTicket == null){
logger.info("Service ticket [{}] does not exist.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
//校验ticket是否过期,是否有效
try{
synchronized(serviceTicket){
//注意这⾥对ticket的判断
if(serviceTicket.isExpired()){
logger.info("ServiceTicket [{}] has expired.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
if(!serviceTicket.isValidFor(service)){
<("Service ticket [{}] with service [{}] does not match supplied service [{}]",
serviceTicketId, Service().getId(), service);
throw new Service());
}
validation框架}
//通过ticket获取到tgt信息,通过tgt和service获取到当前的认证信息
final TicketGrantingTicket root = GrantingTicket().getRoot();
final Authentication authentication =getAuthenticationSatisfiedByPolicy(
root,new Service(), registeredService));
final Principal principal = Principal();
//获取扩展属性信息
final RegisteredServiceAttributeReleasePolicy attributePolicy = AttributeReleasePolicy();
logger.debug("Attribute policy [{}] is associated with service [{}]", attributePolicy, registeredService);
@SuppressWarnings("unchecked")
final Map<String, Object> attributesToRelease = attributePolicy != null
? Attributes(principal): Collections.EMPTY_MAP;
final String principalId = UsernameAttributeProvider().resolveUsername(principal, service);
final Principal modifiedPrincipal =atePrincipal(principalId, attributesToRelease);
final AuthenticationBuilder builder = wInstance(authentication);
builder.setPrincipal(modifiedPrincipal);
//产⽣认证信息,⽤于返回给CAS单点登录客户端
final Assertion assertion =new ImmutableAssertion(
builder.build(),
serviceTicket.isFromNewLogin());
//触发ticket校验事件
doPublishEvent(new CasServiceTicketValidatedEvent(this, serviceTicket, assertion));
return assertion;
}finally{
if(serviceTicket.isExpired()){
this.ticketRegistry.deleteTicket(serviceTicketId);
}
}
}
通过代码的分析主要过程如下:
1、校验service服务
2、根据serviceTicketId获取ServiceTicket
3、判断serviceTicket是否过期
4、判断serviceTicket是否有效
5、认证,得到⽤户信息(包括扩展属性)
6、⽣成断⾔⽤户认证信息
下⾯重点分析⼀下此过程中相关的代码。
3.1. 检查ticket和tgt是否过期
serviceTicket.isExpired()调⽤。此类为AbstractTicket.java在cas-server-core-tickets模块下的org.jasig.cas.ticket包中。在校验中是⽗类ServiceTicketImpl.java中继承了AbstractTicket类。
*AbstractTicket.java*
@MappedSuperclass
public abstract class AbstractTicket implements Ticket, TicketState {
@Override
public final boolean isExpired(){
final TicketGrantingTicket tgt =getGrantingTicket();
pirationPolicy.isExpired(this)
||(tgt != null && tgt.isExpired())
||isExpiredInternal();
}
…………
}
expirationPolicy.isExpired调⽤,通过调⽤MultiTimeUseOrTimeoutExpirationPolicy类来实现失效策略。在l中配置了别名。可以⾃⾏去⽂件中查看。
*MultiTimeUseOrTimeoutExpirationPolicy.java*
@Component("multiTimeUseOrTimeoutExpirationPolicy")
public final class MultiTimeUseOrTimeoutExpirationPolicy extends AbstractCasExpirationPolicy {
/** Serialization support. */
private static final long serialVersionUID =-5704993954986738308L;
/** The time to kill in milliseconds. */
@Value("#{${st.timeToKillInSeconds:10}*1000L}")
private final long timeToKillInMilliSeconds;
/** The maximum number of uses before expiration. */
@Value("${st.numberOfUses:1}")
private final int numberOfUses;
@Override
public boolean isExpired(final TicketState ticketState){
if(ticketState == null){
LOGGER.debug("Ticket state is null for {}",Class().getSimpleName());
return true;
}
//获取当前在使⽤的ticket数量
final long countUses = CountOfUses();
/
/如果使⽤数量⼤于配置⽂件中配置的数量,直接返回失效
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 = LastTimeUsed();
final long difference = systemTime - lastTimeUsed;
//判断ticket是否已经失效
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;
}
}
为调试⽅便或根据需要,可在cas.properties⾥重新配置过期时间和使⽤次数:
st.timeToKillInSeconds=10 过期时间(单位为秒)
st.numberOfUses=1 使⽤次数
3.2. 检查ticket是否有效
ticket的实现类直接使⽤的是ServiceTicketImpl.java,此类在cas-server-core-tickets模块下的org.jasig.cas.ticket包中。ServiceTicketImpl.java
@Entity
@Table(name="SERVICETICKET")
@DiscriminatorColumn(name="TYPE")
@DiscriminatorValue(ServiceTicket.PREFIX)
public class ServiceTicketImpl extends AbstractTicket implements ServiceTicket {
@Override
public boolean isValidFor(final Service serviceToValidate){
//更新serviceTicket相关时间和使⽤次数
updateState();
return serviceToValidate.matches(this.service);
}
…………
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论