CAS单点登录开源框架解读(九)--CAS单点登录客户端认证之服务端验证票据
返回认证信息
服务端如何返回⽤户认证信息
上⼀章节中我们已经知道是通过http请求去到CAS服务端获取信息,根据CAS单点登录客户端的请求地址/rviceValidate,我们再CAS 单点登录服务端上通过Springmvc根据url⾥的/rviceValidate,匹配到@RequestMapping(path="/rviceValidate")
1. ticket校验开始:rviceValidate
/rviceValidate路径所对应的java类为ServiceValidateController.java,在cas-rver-webapp-validation模块下的org.jasig.cas.web 包中。
*ServiceValidateController.java*
@Component("rviceValidateController")
@Controller
public class ServiceValidateController extends AbstractServiceValidateController {
/**
* Handle model and view.
*
* @param request the request
* @param respon the respon
* @return the model and view
* @throws Exception the exception
*/
@RequestMapping(path="/rviceValidate", method = RequestMethod.GET)
@Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request,final HttpServletRespon respon)
throws Exception {
//交给⽗类去处理
return super.handleRequestInternal(request, respon);
}
调⽤⽗类的handleRequestInternal()的⽅法。
2. 调⽤⽗类handleRequestInternal()⽅法
AbstractServiceValidateController.java类在cas-rver-webapp-validation模块下的org.jasig.cas.web包中。
*AbstractServiceValidateController.java*
@Component("rviceValidateController")
public abstract class AbstractServiceValidateController extends AbstractDelegateController {
@Override
protected ModelAndView handleRequestInternal(final HttpServletRequest request,final HttpServletRespon respon)
throws Exception {
//CAS单点登录服务端根据请求获取到rvice
final WebApplicationService rvice =actService(request);
//根据服务端获取的rvice取到rviceId
final String rviceTicketId = rvice != null ? ArtifactId(): null;
//如果rvice为空,或者rviceId为空,返回错误信息给CAS单点登录客户端
if(rvice == null || rviceTicketId == null){
logger.debug("Could not identify rvice and/or rvice ticket for rvice: [{}]", rvice);
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_REQUEST,
CasProtocolConstants.ERROR_CODE_INVALID_REQUEST, null, request, rvice);
呼吸骤停}
try{
TicketGrantingTicket proxyGrantingTicketId = null;
//获取pgturl所对应的认证信息(代理模式下),此时为空
final Credential rviceCredential =getServiceCredentialsFromRequest(rvice, request);
if(rviceCredential != null){
if(rviceCredential != null){
proxyGrantingTicketId =handleProxyGrantingTicketDelivery(rviceTicketId, rviceCredential);
if(proxyGrantingTicketId == null){
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
new Object[]{Id()}, request, rvice);
}
}
//通过认证中⼼校验ticket,并返回认证信息
final Asrtion asrtion =alAuthenticationService.validateServiceTicket(rviceTicketId, rvice);
//根据认证⽤户信息和rvice,如果校验失败返回ticket校验失效信息到CAS单点登录客户端
if(!validateAsrtion(request, rviceTicketId, asrtion)){
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_TICKET,
CasProtocolConstants.ERROR_CODE_INVALID_TICKET, null, request, rvice);
}
//代理模式下的pgtIOU,此时⾮代理模式下获取为空
String proxyIou = null;
if(rviceCredential != null &&this.proxyHandler.canHandle(rviceCredential)){
proxyIou =this.proxyHandler.handle(rviceCredential, proxyGrantingTicketId);
if(StringUtils.isEmpty(proxyIou)){
return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK,
new Object[]{Id()}, request, rvice);
}
}
onSuccessfulValidation(rviceTicketId, asrtion);
logger.debug("Successfully validated rvice ticket {} for rvice [{}]", rviceTicketId, Id());
//返回到CAS服务端成功页⾯,同时把相关参数信息返回给CAS单点登录客户端
return generateSuccessView(asrtion, proxyIou, rvice, proxyGrantingTicketId);
}catch(final AbstractTicketValidationException e){
final String code = e.getCode();
return generateErrorView(code, code,
new Object[]{rviceTicketId, e.getOriginalService().getId(), Id()}, request, rvice);
}catch(final AbstractTicketException te){
return Code(), te.getCode(),
new Object[]{rviceTicketId}, request, rvice);
}catch(final UnauthorizedProxyingException e){
return Message(), e.getMessage(),new Object[]{Id()}, request, rvice);
}catch(final UnauthorizedServiceException e){
return Message(), e.getMessage(), null, request, rvice);
}
}
疫情期间的感人故事…………
注意校验成功和失败时所构建的jsp页⾯是不⼀样的,当成功时请求跳转的页⾯为casServiceValidationSuccess.jsp;⽽当失败时,请求的跳转页⾯为casServiceValidationFailure.jsp。
调⽤中⼼认证服务器校验服务ticket票据:centralAuthenticationService.validateServiceTicket。
3. 调⽤认证中⼼centralAuthenticationService
CentralAuthenticationServiceImpl.java此类在cas-rver-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 Asrtion validateServiceTicket(final String rviceTicketId,final Service rvice)throws AbstractTicketException {
public Asrtion validateServiceTicket(final String rviceTicketId,final Service rvice)throws AbstractTicketException { //根据rvice获取到cas服务端维护的rvice地址买地
final RegisteredService registeredService =this.rvicesManager.findServiceBy(rvice);
//校验获取到的注册rvice是否满⾜CAS单点登录服务端的rvice规则
verifyRegisteredServiceProperties(registeredService, rvice);
//根据rviceId获取到rviceTicket
final ServiceTicket rviceTicket =Ticket(rviceTicketId, ServiceTicket.class);
if(rviceTicket == null){
logger.info("Service ticket [{}] does not exist.", rviceTicketId);
throw new InvalidTicketException(rviceTicketId);
}
//校验ticket是否过期,是否有效
try{
synchronized(rviceTicket){
//注意这⾥对ticket的判断
if(rviceTicket.isExpired()){
卤猪肉logger.info("ServiceTicket [{}] has expired.", rviceTicketId);
throw new InvalidTicketException(rviceTicketId);
}
if(!rviceTicket.isValidFor(rvice)){
<("Service ticket [{}] with rvice [{}] does not match supplied rvice [{}]",
rviceTicketId, Service().getId(), rvice);
throw new Service());
}
}
//通过ticket获取到tgt信息,通过tgt和rvice获取到当前的认证信息
final TicketGrantingTicket root = GrantingTicket().getRoot();
final Authentication authentication =getAuthenticationSatisfiedByPolicy(
root,new Service(), registeredService));
final Principal principal = Principal();
//获取扩展属性信息
final RegisteredServiceAttributeReleaPolicy attributePolicy = AttributeReleaPolicy();
logger.debug("Attribute policy [{}] is associated with rvice [{}]", attributePolicy, registeredService);
@SuppressWarnings("unchecked")
final Map<String, Object> attributesToRelea = attributePolicy != null
? Attributes(principal): Collections.EMPTY_MAP;
final String principalId = UrnameAttributeProvider().resolveUrname(principal, rvice);
final Principal modifiedPrincipal =atePrincipal(principalId, attributesToRelea);
final AuthenticationBuilder builder = wInstance(authentication);
相爱的两个人builder.tPrincipal(modifiedPrincipal);
//产⽣认证信息,⽤于返回给CAS单点登录客户端
final Asrtion asrtion =new ImmutableAsrtion(
酸菜鱼的家常做法builder.build(),
rviceTicket.isFromNewLogin());
//触发ticket校验事件
doPublishEvent(new CasServiceTicketValidatedEvent(this, rviceTicket, asrtion));
return asrtion;
}finally{
if(rviceTicket.isExpired()){
this.ticketRegistry.deleteTicket(rviceTicketId);
}
}玫瑰花手工制作
}
通过代码的分析主要过程如下:
1、校验rvice服务
2、根据rviceTicketId获取ServiceTicket
3、判断rviceTicket是否过期
4、判断rviceTicket是否有效
5、认证,得到⽤户信息(包括扩展属性)
6、⽣成断⾔⽤户认证信息
下⾯重点分析⼀下此过程中相关的代码。
3.1. 检查ticket和tgt是否过期
rviceTicket.isExpired()调⽤。此类为AbstractTicket.java在cas-rver-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调⽤,通过调⽤MultiTimeUOrTimeoutExpirationPolicy类来实现失效策略。在l中配置了别名。可以⾃⾏去⽂件中查看。
*MultiTimeUOrTimeoutExpirationPolicy.java*
@Component("multiTimeUOrTimeoutExpirationPolicy")
public final class MultiTimeUOrTimeoutExpirationPolicy extends AbstractCasExpirationPolicy {
/** Serialization support. */
private static final long rialVersionUID =-5704993954986738308L;
/** The time to kill in milliconds. */
@Value("#{${st.timeToKillInSeconds:10}*1000L}")
private final long timeToKillInMilliSeconds;
/** The maximum number of us before expiration. */
畏缩@Value("${st.numberOfUs:1}")
private final int numberOfUs;
@Override
public boolean isExpired(final TicketState ticketState){
if(ticketState == null){
LOGGER.debug("Ticket state is null for {}",Class().getSimpleName());
return true;
}
//获取当前在使⽤的ticket数量
final long countUs = CountOfUs();
/
/如果使⽤数量⼤于配置⽂件中配置的数量,直接返回失效
if(countUs >=this.numberOfUs){
LOGGER.debug("Ticket usage count {} is greater than or equal to {}", countUs,this.numberOfUs);
return true;
}
final long systemTime = System.currentTimeMillis();
final long lastTimeUd = LastTimeUd();
final long difference = systemTime - lastTimeUd;
//判断ticket是否已经失效
if(difference >=this.timeToKillInMilliSeconds){
LOGGER.debug("Ticket has expired becau the difference between current time [{}] "
+"and ticket time [{}] is greater than or equal to [{}]", systemTime, lastTimeUd,
this.timeToKillInMilliSeconds);
return true;
}
return fal;
}
}
为调试⽅便或根据需要,可在cas.properties⾥重新配置过期时间和使⽤次数:
st.timeToKillInSeconds=10 过期时间(单位为秒)
st.numberOfUs=1 使⽤次数
3.2. 检查ticket是否有效
ticket的实现类直接使⽤的是ServiceTicketImpl.java,此类在cas-rver-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 rviceToValidate){
//更新rviceTicket相关时间和使⽤次数
updateState();
return rviceToValidate.matches(this.rvice);
}
…………
}