简介 项目中使用的CAS后续需要外部系统的接入,这里需要解决两个问题,1:单点登出问题,之前的文章中提到各个接入cas的应用在验证st时会获取到用户信息,然后将用户信息做缓存,下次再次访问这个子应用时就不需要再向cas去获取是否登录,那么这就存在一个问题,CAS已经退出登录,但是各个子应用仍然保存着登录状态,这就导致了用户仍然可以访问子应用。第二个问题,由于在验证st时,cas会返回用户信息,但是cas默认返回的信息有并不能满足项目需求,有些是多余的,还有一些自定义的用户信息并没有返回,这里也需要对信息做重新定制.
单点登出 根据官网文档 https://apereo.github.io/cas/5.1.x/installation/Logout-Single-Signout.html slo退出分为2种,Back Channel 和Front Channel,我这里使用的是Back Channel,其原理也很简单就是在每个service下配置一个回调地址,当触发退出时,如果这个服务验证过st,那么系统就会认为其已经保存了登录信息,那么在触发单点登出时,调用配置的退出地址。
配置service
我这里没有使用静态json配置service的方法,在resource/services 下添加你要接入cas的配置文件,文件名为 localhost-10000003.json,其中这里的localhost必须和下面配置文件的name对上,相应的10000003也必须和下面配置文件中的id对上,evaluationOrder为配置优先级,当一个接入的url同事满足多个配置文件时,值越小,优先级越高,还有一个配置单点登出的重要参数logoutUrl,这个就是触发登出时系统要去调用的子应用的回调地址,但实践当中我们的回调地址往往都需要带上一些凭证信息如用户id,所以用户id是动态生成的,CAS默认提供的逻辑只是静态的去调用配置的回调接口,我们要的是自定义登出逻辑,在配置的回调地址中加上参数,这里是加上用户id
1 2 3 4 5 6 7 8 9 10 11 { "@class" : "org.apereo.cas.services.RegexRegisteredService", "serviceId" : "http://127.0.0.1.*", "name" : "localhost", "id" : 10000003, "description" : "localhost", "evaluationOrder" : 1, "theme":"theme_default", "logoutUrl":"http://127.0.0.1:11202/logout" }
定制单点登出回调逻辑
官网并没有对如何实现单点登出给出更多的文档,因为cas是通过webflow来实现的,通过跟踪具体的webflow配置信息就可以看出他的逻辑
DefaultLogoutWebflowConfigurer 这个类是单点退出的webflow配置入口,在这个类中,可以找到以下代码,其中terminateSessionAction就是处理单点退出所在的类1 ActionState actionState = this.createActionState(flow, "terminateSession", "terminateSessionAction");
我们进入TerminateSessionAction 这个类 在terminate方法下有一行,根据方法名我们可以知道这里执行的是销毁TGT凭证的地方
1 List<LogoutRequest> logoutRequests = this.centralAuthenticationService.destroyTicketGrantingTicket(tgtId);
我们找到CentralAuthenticationService 这个接口,发现其默认只有一个实现类DefaultCentralAuthenticationService,在其的destroyTicketGrantingTicket方法中我们可以看到,如下代码,通过查看源码,发现this.logoutManager.performLogout(ticket)中执行的就是单点退出的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public List<LogoutRequest> destroyTicketGrantingTicket(String ticketGrantingTicketId) { try { LOGGER.debug("Removing ticket [{}] from registry...", ticketGrantingTicketId); TicketGrantingTicket ticket = (TicketGrantingTicket)this.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class); LOGGER.debug("Ticket found. Processing logout requests and then deleting the ticket..."); AuthenticationCredentialsThreadLocalBinder.bindCurrent(ticket.getAuthentication()); List<LogoutRequest> logoutRequests = this.logoutManager.performLogout(ticket); this.deleteTicket(ticketGrantingTicketId); this.doPublishEvent(new CasTicketGrantingTicketDestroyedEvent(this, ticket)); return logoutRequests; } catch (InvalidTicketException var4) { LOGGER.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId); return new ArrayList(0); } }
我们进入其唯一实现类DefaultLogoutManager,找到 performLogout方法,下面我做注释,注释的地方就是执行单点登出的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public List<LogoutRequest> performLogout(TicketGrantingTicket ticket) { LOGGER.info("Performing logout operations for [{}]", ticket.getId()); if (this.singleLogoutCallbacksDisabled) { LOGGER.info("Single logout callbacks are disabled"); return new ArrayList(0); } else { //执行单点退出逻辑 List<LogoutRequest> logoutRequests = this.performLogoutForTicket(ticket); this.logoutExecutionPlan.getLogoutHandlers().forEach((h) -> { LOGGER.debug("Invoking logout handler [{}] to process ticket [{}]", h.getClass().getSimpleName(), ticket.getId()); h.handle(ticket); }); LOGGER.info("[{}] logout requests were processed", logoutRequests.size()); return logoutRequests; } }
现在我们再进入performLogoutForTicket 方法,下面注释出已经标出了执行单点退出的地址
1 2 3 4 5 6 7 8 9 10 11 private List<LogoutRequest> performLogoutForTicket(TicketGrantingTicket ticketToBeLoggedOut) { Stream<Map<String, Service>> streamServices = Stream.concat(Stream.of(ticketToBeLoggedOut.getServices()), Stream.of(ticketToBeLoggedOut.getProxyGrantingTickets())); return (List)streamServices.map(Map::entrySet).flatMap(Collection::stream).filter((entry) -> { return entry.getValue() instanceof WebApplicationService; }).map((entry) -> { WebApplicationService service = (WebApplicationService)entry.getValue(); LOGGER.debug("Handling single logout callback for [{}]", service); //具体执行单点退出的地方 return this.singleLogoutServiceMessageHandler.handle(service, (String)entry.getKey()); }).flatMap(Collection::stream).filter(Objects::nonNull).collect(Collectors.toList()); }
现在我们思路就很明确了,实现自己的SingleLogoutServiceMessageHandler,并注入到DefaultLogoutManager中
单点登出实践
重写SingleLogoutServiceMessageHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class LogoutHandler implements SingleLogoutServiceMessageHandler { private HttpClient httpClient; private ServicesManager servicesManager; private TicketRegistry ticketRegistry; public LogoutHandler(HttpClient httpClient, ServicesManager servicesManager, TicketRegistry ticketRegistry) { this.httpClient = httpClient; this.servicesManager = servicesManager; this.ticketRegistry = ticketRegistry; } @Override public Collection<LogoutRequest> handle(WebApplicationService singleLogoutService, String ticketId) { //根据ticketId获取到ST ServiceTicket serviceTicket =(ServiceTicket)ticketRegistry.getTicket(ticketId); //根据ST获取到TGT TicketGrantingTicket ticketGrantingTicket=serviceTicket.getTicketGrantingTicket(); //用TGT获取用户id String userId=ticketGrantingTicket.getAuthentication().getPrincipal().getId(); Collection<LogoutRequest> logoutRequests=new ArrayList<>(); //获取当前有多少服务使用TGT去生成ST,因为单点的统一凭证就是TGT,一次登录使用的都是同一个TGT,所以可以使用TGT发现有多少个自服务登录过系统 for(Map.Entry<String, Service> entry: ticketGrantingTicket.getServices().entrySet()){ //查询对应系统配置的信息 RegisteredService registeredService = servicesManager.findServiceBy(entry.getValue()); //获取到前面配置的service的回调地址并且加上userId作为参数 String url=registeredService.getLogoutUrl()+"?userId="+userId; try { //发送消息 HttpMessage message =httpClient.sendMessageToEndPoint(new URL(url)); DefaultLogoutRequest defaultLogoutRequest=new DefaultLogoutRequest(ticketId,singleLogoutService,new URL(url)); logoutRequests.add(defaultLogoutRequest); } catch (MalformedURLException e) { e.printStackTrace(); } } return logoutRequests; } }
在Srping的LogoutManager中注入的LogoutHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Autowired private LogoutMessageCreator logoutBuilder; @Autowired @Qualifier("noRedirectHttpClient") private HttpClient httpClient; @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @Autowired @Qualifier("ticketRegistry") private ObjectProvider<TicketRegistry> ticketRegistry; @Autowired @Bean public LogoutManager logoutManager(@Qualifier("logoutExecutionPlan") final LogoutExecutionPlan logoutExecutionPlan) { return new DefaultLogoutManager(logoutBuilder, new LogoutHandler(httpClient,servicesManager,ticketRegistry.getIfAvailable()), false , logoutExecutionPlan); }
自定义登录成功后的返回信息 在cas中用户登录信息是用过验证st时返回,但是默认返回的信息包含很多我们不需要的信息,在第三方接入时难免体验很差,所以我这边对返回的用户信息进行了改造
找到入口 之前的的文章中就已经找到验证st的接口地址是/p3/serviceValidate,我们在源码中搜索此地址找到验证st的入口 V3ServiceValidateController
1 2 3 4 5 6 @GetMapping( path = {"/p3/serviceValidate"} ) protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception { return super.handleRequestInternal(request, response); }
我第一步想找到的是,接口的参数是如何渲染出来的,使用idea一步步进入方法,发现其生成参数的地方是在其父类 AbstractServiceValidateController的 getModelAndView方法下,我们可以看到根据传参的不同可以生成json格式的view和xml模式的view,而successView是通过构造方法注入到这个类的,那我们只需要在源码去全局搜索AbstractServiceValidateController的注入的successView到底是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 private ModelAndView getModelAndView(HttpServletRequest request, boolean isSuccess, WebApplicationService service) { ValidationResponseType type = service != null ? service.getFormat() : ValidationResponseType.XML; String format = request.getParameter("format"); if (!StringUtils.isEmpty(format)) { try { type = ValidationResponseType.valueOf(format.toUpperCase()); } catch (Exception var7) { LOGGER.warn(var7.getMessage(), var7); } } return type == ValidationResponseType.JSON ? new ModelAndView(this.jsonView) : new ModelAndView(isSuccess ? this.successView : this.failureView); }
在源码中我发现其配置类在CasValidationConfiguration下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Bean @ConditionalOnMissingBean(name = "v3ServiceValidateController") public V3ServiceValidateController v3ServiceValidateController() { return new V3ServiceValidateController( cas20WithoutProxyProtocolValidationSpecification, authenticationSystemSupport.getIfAvailable(), servicesManager, centralAuthenticationService, proxy20Handler.getIfAvailable(), argumentExtractor.getIfAvailable(), multifactorTriggerSelectionStrategy, authenticationContextValidator, cas3ServiceJsonView(), cas3ServiceSuccessView(), cas3ServiceFailureView, casProperties.getAuthn().getMfa().getAuthenticationContextAttribute(), serviceValidationAuthorizers, casProperties.getSso().isRenewAuthnEnabled() ); }
我们进一步查看cas3ServiceSuccessView()这个方法,又发现其是Cas30ResponseView,其中我们关注下面这个参数cas3SuccessView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Bean @ConditionalOnMissingBean(name = "cas3ServiceSuccessView") public View cas3ServiceSuccessView() { final String authenticationContextAttribute = casProperties.getAuthn().getMfa().getAuthenticationContextAttribute(); final boolean isReleaseProtocolAttributes = casProperties.getAuthn().isReleaseProtocolAttributes(); return new Cas30ResponseView(true, protocolAttributeEncoder, servicesManager, authenticationContextAttribute, cas3SuccessView, isReleaseProtocolAttributes, authenticationAttributeReleasePolicy, authenticationServiceSelectionPlan.getIfAvailable(), cas3ProtocolAttributesRenderer()); }
我们找到了 cas3SuccessView这个类
1 2 3 4 5 6 @Bean @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public CasProtocolView cas3SuccessView() { return new CasProtocolView(casProperties.getView().getCas3().getSuccess(), applicationContext, springTemplateEngine, thymeleafProperties); }
Cas30ViewProperties 我们一步步进去查看发现重要信息protocol/3.0/casServiceValidationSuccess
1 2 3 4 5 6 public class Cas30ViewProperties implements Serializable { private static final long serialVersionUID = 2345062034300650858L; private String success = "protocol/3.0/casServiceValidationSuccess"; private String failure = "protocol/3.0/casServiceValidationFailure"; ... }
我们进入源码目录下找到此目录下的文件,我们发现其实cas是使用thymeleaf将xml渲染出来而已
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'> <cas:authenticationSuccess> <cas:user th:text="${principal.id}"/> <cas:proxyGrantingTicket th:if="${pgtIou}" th:text="${pgtIou}"/> <cas:proxies th:if="${not #lists.isEmpty(chainedAuthentications)}"> <cas:proxy th:each="proxy : ${chainedAuthentications}" th:text="${proxy.principal.id}"/> </cas:proxies> <cas:attributes th:if="${not #lists.isEmpty(formattedAttributes)}"> <div th:each="attr : ${formattedAttributes}" th:remove="tag"> <div th:utext="${attr}" th:remove="tag"/> </div> </cas:attributes> </cas:authenticationSuccess> </cas:serviceResponse>
定制参数 现在我们知道了参数是如何渲染出来的下面我们就要开支定制参数了 同样在AbstractServiceValidateController类中发现generateSuccessView方法,使用过SpringMVC的一定都能看懂以下代码,那么我们现在寻找绑定modal的Assertion 参数是如何生成的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private ModelAndView generateSuccessView(Assertion assertion, String proxyIou, WebApplicationService service, HttpServletRequest request, Optional<MultifactorAuthenticationProvider> contextProvider, TicketGrantingTicket proxyGrantingTicket) { ModelAndView modelAndView = this.getModelAndView(request, true, service); //将数据绑定到modal中 modelAndView.addObject("assertion", assertion); modelAndView.addObject("service", service); if (StringUtils.hasText(proxyIou)) { modelAndView.addObject("pgtIou", proxyIou); } if (proxyGrantingTicket != null) { modelAndView.addObject("proxyGrantingTicket", proxyGrantingTicket.getId()); } contextProvider.ifPresent((provider) -> { modelAndView.addObject(this.authnContextAttribute, provider.getId()); }); Map<String, ?> augmentedModelObjects = this.augmentSuccessViewModelObjects(assertion); if (augmentedModelObjects != null) { modelAndView.addAllObjects(augmentedModelObjects); } return modelAndView; }
我们在generateSuccessView的调用者handleTicketValidation发现了以下方法,这里生成了Assertion
1 Assertion assertion = this.validateServiceTicket(service, serviceTicketId);
我们再进入validateServiceTicket方法,发现我们的类其实是由CentralAuthenticationService生成的
1 2 3 protected Assertion validateServiceTicket(WebApplicationService service, String serviceTicketId) { return this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service); }
我们再进入其默认实现类DefaultCentralAuthenticationService的validateServiceTicket方法,这里我们看到主要逻辑是这边的代码会获取原来的Principal,然后将其的attributes重新生成了一次,这里就是为什么我之前在自定义认证时生成的Principal中的attributes丢失的原因,那么我们这里解决办法也很明了了,重新CentralAuthenticationService的方法将我们需要的参数放到新的Principal的attributes中即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public Assertion validateServiceTicket(String serviceTicketId, Service service) throws AbstractTicketException { if (!this.isTicketAuthenticityVerified(serviceTicketId)) { LOGGER.info("Service ticket [{}] is not a valid ticket issued by CAS.", serviceTicketId); throw new InvalidTicketException(serviceTicketId); } else { ServiceTicket serviceTicket = (ServiceTicket)this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class); if (serviceTicket == null) { LOGGER.warn("Service ticket [{}] does not exist.", serviceTicketId); throw new InvalidTicketException(serviceTicketId); } else { Assertion var18; try { synchronized(serviceTicket) { if (serviceTicket.isExpired()) { LOGGER.info("ServiceTicket [{}] has expired.", serviceTicketId); throw new InvalidTicketException(serviceTicketId); } if (!serviceTicket.isValidFor(service)) { LOGGER.error("Service ticket [{}] with service [{}] does not match supplied service [{}]", new Object[]{serviceTicketId, serviceTicket.getService().getId(), service}); throw new UnrecognizableServiceForServiceTicketValidationException(serviceTicket.getService()); } } Service selectedService = this.resolveServiceFromAuthenticationRequest(serviceTicket.getService()); LOGGER.debug("Resolved service [{}] from the authentication request", selectedService); RegisteredService registeredService = this.servicesManager.findServiceBy(selectedService); LOGGER.debug("Located registered service definition [{}] from [{}] to handle validation request", registeredService, selectedService); RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(selectedService, registeredService); TicketGrantingTicket root = serviceTicket.getTicketGrantingTicket().getRoot(); Authentication authentication = this.getAuthenticationSatisfiedByPolicy(root.getAuthentication(), new ServiceContext(selectedService, registeredService)); //获取旧的Principal Principal principal = authentication.getPrincipal(); RegisteredServiceAttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy(); LOGGER.debug("Attribute policy [{}] is associated with service [{}]", attributePolicy, registeredService); //生成新的attributesToRelease Map<String, Object> attributesToRelease = attributePolicy != null ? attributePolicy.getAttributes(principal, selectedService, registeredService) : new HashMap(); LOGGER.debug("Calculated attributes for release per the release policy are [{}]", ((Map)attributesToRelease).keySet()); String principalId = registeredService.getUsernameAttributeProvider().resolveUsername(principal, selectedService, registeredService); //生成新的Principal类 Principal modifiedPrincipal = this.principalFactory.createPrincipal(principalId, (Map)attributesToRelease); AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(authentication); builder.setPrincipal(modifiedPrincipal); LOGGER.debug("Principal determined for release to [{}] is [{}]", registeredService.getServiceId(), principalId); Authentication finalAuthentication = builder.build(); AuditableContext audit = AuditableContext.builder().service(selectedService).authentication(finalAuthentication).registeredService(registeredService).retrievePrincipalAttributesFromReleasePolicy(Boolean.FALSE).build(); AuditableExecutionResult accessResult = this.registeredServiceAccessStrategyEnforcer.execute(audit); accessResult.throwExceptionIfNeeded(); AuthenticationCredentialsThreadLocalBinder.bindCurrent(finalAuthentication); Assertion assertion = (new DefaultAssertionBuilder(finalAuthentication)).with(selectedService).with(serviceTicket.getTicketGrantingTicket().getChainedAuthentications()).with(serviceTicket.isFromNewLogin()).build(); this.doPublishEvent(new CasServiceTicketValidatedEvent(this, serviceTicket, assertion)); var18 = assertion; } finally { if (serviceTicket.isExpired()) { this.deleteTicket(serviceTicketId); } else { this.ticketRegistry.updateTicket(serviceTicket); } } return var18; } } }
具体实践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class RestCentralAuthenticationService extends DefaultCentralAuthenticationService{ public RestCentralAuthenticationService(ApplicationEventPublisher applicationEventPublisher, TicketRegistry ticketRegistry, ServicesManager servicesManager, LogoutManager logoutManager, TicketFactory ticketFactory, AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies, ContextualAuthenticationPolicyFactory<ServiceContext> serviceContextAuthenticationPolicyFactory, PrincipalFactory principalFactory, CipherExecutor<String, String> cipherExecutor, AuditableExecution registeredServiceAccessStrategyEnforcer) { super(applicationEventPublisher, ticketRegistry, servicesManager, logoutManager, ticketFactory, authenticationRequestServiceSelectionStrategies, serviceContextAuthenticationPolicyFactory, principalFactory, cipherExecutor, registeredServiceAccessStrategyEnforcer); } public Assertion validateServiceTicket(String serviceTicketId, Service service) throws AbstractTicketException { if (!this.isTicketAuthenticityVerified(serviceTicketId)) { throw new InvalidTicketException(serviceTicketId); } else { ServiceTicket serviceTicket = (ServiceTicket)this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class); if (serviceTicket == null) { throw new InvalidTicketException(serviceTicketId); } else { Assertion var18; try { synchronized(serviceTicket) { if (serviceTicket.isExpired()) { throw new InvalidTicketException(serviceTicketId); } if (!serviceTicket.isValidFor(service)) { throw new UnrecognizableServiceForServiceTicketValidationException(serviceTicket.getService()); } } Service selectedService = this.resolveServiceFromAuthenticationRequest(serviceTicket.getService()); RegisteredService registeredService = this.servicesManager.findServiceBy(selectedService); TicketGrantingTicket root = serviceTicket.getTicketGrantingTicket().getRoot(); Authentication authentication = this.getAuthenticationSatisfiedByPolicy(root.getAuthentication(), new ServiceContext(selectedService, registeredService)); Principal principal = authentication.getPrincipal(); RegisteredServiceAttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy(); Map<String, Object> attributesToRelease = attributePolicy != null ? attributePolicy.getAttributes(principal, selectedService, registeredService) : new HashMap(); //将我们需要回传给用户的信息放入新的attributes中 attributesToRelease.putAll(principal.getAttributes()); attributesToRelease.put("tgt",root.getId()); String principalId = registeredService.getUsernameAttributeProvider().resolveUsername(principal, selectedService, registeredService); Principal modifiedPrincipal = this.principalFactory.createPrincipal(principalId, (Map)attributesToRelease); AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(authentication); builder.setPrincipal(modifiedPrincipal); Authentication finalAuthentication = builder.build(); AuditableContext audit = AuditableContext.builder().service(selectedService).authentication(finalAuthentication).registeredService(registeredService).retrievePrincipalAttributesFromReleasePolicy(Boolean.FALSE).build(); AuditableExecutionResult accessResult = this.registeredServiceAccessStrategyEnforcer.execute(audit); accessResult.throwExceptionIfNeeded(); AuthenticationCredentialsThreadLocalBinder.bindCurrent(finalAuthentication); Assertion assertion = (new DefaultAssertionBuilder(finalAuthentication)).with(selectedService).with(serviceTicket.getTicketGrantingTicket().getChainedAuthentications()).with(serviceTicket.isFromNewLogin()).build(); this.doPublishEvent(new CasServiceTicketValidatedEvent(this, serviceTicket, assertion)); var18 = assertion; } finally { if (serviceTicket.isExpired()) { this.deleteTicket(serviceTicketId); } else { this.ticketRegistry.updateTicket(serviceTicket); } } return var18; } } } }
同样将我们重新的CentralAuthenticationService类注入Spring取代默认的CentralAuthenticationService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Autowired @Qualifier("ticketRegistry") private ObjectProvider<TicketRegistry> ticketRegistry; @Autowired @Qualifier("servicesManager") private ObjectProvider<ServicesManager> servicesManagers; @Autowired @Qualifier("logoutManager") private ObjectProvider<LogoutManager> logoutManager; @Autowired @Qualifier("defaultTicketFactory") private ObjectProvider<TicketFactory> ticketFactory; @Autowired private ContextualAuthenticationPolicyFactory<ServiceContext> authenticationPolicyFactory; @Autowired @Qualifier("principalFactory") private ObjectProvider<PrincipalFactory> principalFactory; @Autowired @Qualifier("protocolTicketCipherExecutor") private ObjectProvider<CipherExecutor> cipherExecutor; @Autowired @Qualifier("registeredServiceAccessStrategyEnforcer") private AuditableExecution registeredServiceAccessStrategyEnforcer; @Bean @Autowired public CentralAuthenticationService centralAuthenticationService( @Qualifier("authenticationServiceSelectionPlan") final AuthenticationServiceSelectionPlan authenticationServiceSelectionPlan) { return new RestCentralAuthenticationService(applicationEventPublisher, ticketRegistry.getIfAvailable(), servicesManagers.getIfAvailable(), logoutManager.getIfAvailable(), ticketFactory.getIfAvailable(), authenticationServiceSelectionPlan, authenticationPolicyFactory, principalFactory.getIfAvailable(), cipherExecutor.getIfAvailable(), registeredServiceAccessStrategyEnforcer); }
现在我们修改protocol/3.0/casServiceValidationSuccess文件,将我们的attributes作为模板放入xml即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <response> <id th:text="${principal.id}"/> <name th:text="${principal.attributes.name}"/> <type th:text="${principal.attributes.type}"/> <tgt th:text="${principal.attributes.tgt}"/> <token th:if="${principal.attributes.token!=null}" th:text="${principal.attributes.token}"/> <idCard th:if="${principal.attributes.idCard!=null}" th:text="${principal.attributes.idCard}"/> <jbrName th:if="${principal.attributes.jbrName!=null}" th:text="${principal.attributes.jbrName}"/> <jbrIdCard th:if="${principal.attributes.jbrIdCard!=null}" th:text="${principal.attributes.jbrIdCard}"/> <legalpersonCompanyMan th:if="${principal.attributes.legalpersonCompanyMan!=null}" th:text="${principal.attributes.legalpersonCompanyMan}"/> <legalpersonName th:if="${principal.attributes.legalpersonName!=null}" th:text="${principal.attributes.legalpersonName}"/> <legalpersonIdCard th:if="${principal.attributes.legalpersonIdCard!=null}" th:text="${principal.attributes.legalpersonIdCard}"/> <jbrMobile th:if="${principal.attributes.jbrMobile!=null}" th:text="${principal.attributes.jbrMobile}"/> <code>200</code> </response>