本文共 25343 字,大约阅读时间需要 84 分钟。
原文地址,转载请注明出处: ©
前面我们整合客户端的时候,需要在cas服务端注册,使用的是json文件的方式,更简单的一点,直接配置为只要是http或者https的请求,都表示注册,那也就没有本篇的动态添加services了,哈哈 不过,这也是一个不错的方法。
假如,我们以域名配置的,比如: 注册,那么又有新的模块为 我们总不能每次修改配置,重启cas服务吧。这很不现实,官网给出了如下的解决方式,将数据库来存储这些数据。
org.apereo.cas cas-server-support-jpa-service-registry ${cas.version} org.apereo.cas cas-server-core-services-api ${cas.version} org.apereo.cas cas-server-core-authentication-attributes ${cas.version}
#数据库用户名cas.serviceRegistry.jpa.user=root#数据库密码cas.serviceRegistry.jpa.password=123456#mysql驱动cas.serviceRegistry.jpa.driverClass=com.mysql.jdbc.Driver#数据库连接cas.serviceRegistry.jpa.url=jdbc:mysql://127.0.0.1:3306/testshiro?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=falsecas.serviceRegistry.dialect=org.hibernate.dialect.MySQLDialect#连接池配置cas.serviceRegistry.jpa.pool.suspension=falsecas.serviceRegistry.jpa.pool.minSize=6cas.serviceRegistry.jpa.pool.maxSize=18cas.serviceRegistry.jpa.pool.maxWait=2000cas.serviceRegistry.jpa.pool.timeoutMillis=1000#默认为create-drop,表示每次启动服务都会清除你之前注册的cas服务cas.serviceRegistry.jpa.ddlAuto=create-drop
为了方便测试,将服务注册只允许app1.cas.com
{ "@class" : "org.apereo.cas.services.RegexRegisteredService", "serviceId" : "^(https|imaps|http)://app1.cas.com.*", "name" : "测试客户端", "id" : 10000001, "description" : "这是一个测试客户端的服务,所有的https访问都允许通过", "evaluationOrder" : 10000}
这里为了方便直接在地址栏操作增加或删除。代码如下:
package com.wangsaichao.cas.controller;import org.apereo.cas.services.*;import org.json.JSONObject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import java.net.URL;/** * @author: wangsaichao * @date: 2018/8/10 * @description: */@RestControllerpublic class ServiceController { private Logger logger = LoggerFactory.getLogger(ServiceController.class); @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; /** * 注册service * @param serviceId 域名 * @param id 顺序 * @return */ @RequestMapping(value = "/addClient/{serviceId}/{id}",method = RequestMethod.GET) public Object addClient(@PathVariable("serviceId") String serviceId, @PathVariable("id") int id) { try { String a="^(https|imaps|http)://"+serviceId+".*"; RegexRegisteredService service = new RegexRegisteredService(); ReturnAllAttributeReleasePolicy re = new ReturnAllAttributeReleasePolicy(); service.setServiceId(a); service.setId(id); service.setAttributeReleasePolicy(re); service.setName("login"); //这个是为了单点登出而作用的 service.setLogoutUrl(new URL("http://"+serviceId)); servicesManager.save(service); //执行load让他生效 servicesManager.load(); ReturnMessage returnMessage = new ReturnMessage(); returnMessage.setCode(200); returnMessage.setMessage("添加成功"); return returnMessage; } catch (Exception e) { logger.error("注册service异常",e); ReturnMessage returnMessage = new ReturnMessage(); returnMessage.setCode(500); returnMessage.setMessage("添加失败"); return returnMessage; } } /** * 删除service异常 * @param serviceId * @return */ @RequestMapping(value = "/deleteClient/{serviceId}/{id}",method = RequestMethod.GET) public Object deleteClient(@PathVariable("serviceId") String serviceId,@PathVariable("id") int id) { try {// String a="^(https|imaps|http)://"+serviceId+".*";// String a="^(https|imaps|http)://"+serviceId+".*";// RegexRegisteredService service = new RegexRegisteredService();// ReturnAllAttributeReleasePolicy re = new ReturnAllAttributeReleasePolicy();// service.setServiceId(a);// service.setId(id);// service.setAttributeReleasePolicy(re);// service.setName("login");// //这个是为了单点登出而作用的// service.setLogoutUrl(new URL("http://"+serviceId)); String aa = "http://app2.cas.com:8082"; RegisteredService service = servicesManager.findServiceBy(aa); servicesManager.delete(service); //执行load生效 servicesManager.load(); ReturnMessage returnMessage = new ReturnMessage(); returnMessage.setCode(200); returnMessage.setMessage("删除成功"); return returnMessage; } catch (Exception e) { logger.error("删除service异常",e); ReturnMessage returnMessage = new ReturnMessage(); returnMessage.setCode(500); returnMessage.setMessage("删除失败"); return returnMessage; } } public class ReturnMessage{ private Integer code; private String message; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }}
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘type=MyISAM’ at line 5
原因是上面的配置:cas.serviceRegistry.dialect=org.hibernate.dialect.MySQLDialect
应该改为: cas.serviceRegistry.dialect=org.hibernate.dialect.MySQL5Dialect
为了避免重启服务,导致之前的services丢失,需要将
cas.serviceRegistry.jpa.ddlAuto=update
每次启动之后,会在mysql中自动生成以下表格 在添加service的时候没问题,但是在删除service的时候报以下异常:
java.lang.IllegalArgumentException: ‘actionPerformed’ cannot be null. Check the correctness of @Audit annotation at the following audit point: execution(public synchronized org.apereo.cas.services.RegisteredService 经过debug发现,代码其实是没问题的,可以正常删除,但是在执行AuditTrailManagementAspect类中下面这段代码的时候报异常: 我们发现auditActionResolver和auditResourceResolver是null,经过搜索,找到了下面这篇文章,在此感谢作者,博客地址:搜了一下Audit这玩意叫什么审计日志,google了半天也没看到源码(github上面有个,但cas的应该是在此基础上修改过的),无奈之下看了 WEB-INF/spring-configuration/auditTrailContext.xml 这个文件,这才发现,里面根本就没有 DELETE_SERVICE_ACTION_RESOLVER和DELETE_SERVICE_RESOURCE_RESOLVER这两个值对应的Resolver。。。 这可能跟cas的版本或者编译情况有关,真是不明白为什么save方法有但是delete方法就没有了,在这上面卡了好久。
因为我用的是cas5.3.2是springboot版本的,已经没有文中所说的那个文件了,通过查看源码,打算使用以下方式解决。
自定义starter,跟之前自定义cas-client-starter一样。中间整合的过程,出现很多错误,没有一步一步,记录,到时候请看源码。只是依赖了原始audit的api包,至于下面的CasCoreAuditConfiguration类是从原始cas-server-core-audit包中拷贝出来的,然后需要自定义一个cas-server-core-audit的starter包来覆盖原来的包,这个包必须也叫cas-server-core-audit因为在cas-server-webapp${app.server}中依赖了这个包,并且scope是runtime类型的,没办法,只能写一个一模一样的包了。4.0.0 org.apereo.cas cas-server-core-audit 5.3.2 jar cas-server-core-audit cas-server-core-audit org.springframework.boot spring-boot-starter-parent 2.0.0.RELEASE 5.3.2 org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-autoconfigure org.apereo.cas cas-server-core-api-configuration-model ${cas.version} org.apereo.cas cas-server-core-util-api ${cas.version} org.apereo.cas cas-server-core-audit-api ${cas.version} org.aspectj aspectjrt 1.9.1 javax.servlet servlet-api 2.5 org.apache.maven.plugins maven-compiler-plugin 3.5.1 maven-source-plugin 2.1 true compile jar
package org.apereo.cas.audit.spi.config;import org.apache.commons.lang3.StringUtils;import org.apereo.cas.audit.*;import org.apereo.cas.audit.spi.*;import org.apereo.cas.configuration.CasConfigurationProperties;import org.apereo.cas.configuration.model.core.audit.AuditProperties;import org.apereo.cas.configuration.model.core.audit.AuditSlf4jLogProperties;import org.apereo.cas.util.CollectionUtils;import org.apereo.inspektr.audit.AuditTrailManagementAspect;import org.apereo.inspektr.audit.spi.AuditActionResolver;import org.apereo.inspektr.audit.spi.AuditResourceResolver;import org.apereo.inspektr.audit.spi.support.DefaultAuditActionResolver;import org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager;import org.apereo.inspektr.common.spi.PrincipalResolver;import org.apereo.inspektr.common.web.ClientInfoThreadLocalFilter;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;import org.springframework.core.Ordered;import org.springframework.core.annotation.AnnotationAwareOrderComparator;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * This is {@link CasCoreAuditConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */@Configuration("casCoreAuditConfiguration")@EnableAspectJAutoProxy@EnableConfigurationProperties(CasConfigurationProperties.class)public class CasCoreAuditConfiguration implements AuditTrailExecutionPlanConfigurer, AuditTrailRecordResolutionPlanConfigurer { private static final Logger LOGGER = LoggerFactory.getLogger(CasCoreAuditConfiguration.class); private static final String AUDIT_ACTION_SUFFIX_FAILED = "_FAILED"; @Autowired private CasConfigurationProperties casProperties; @Autowired private ApplicationContext applicationContext; @Bean public AuditTrailManagementAspect auditTrailManagementAspect(@Qualifier("auditTrailExecutionPlan") final AuditTrailExecutionPlan auditTrailExecutionPlan, @Qualifier("auditTrailRecordResolutionPlan") final AuditTrailRecordResolutionPlan auditTrailRecordResolutionPlan) { final AuditTrailManagementAspect aspect = new AuditTrailManagementAspect( casProperties.getAudit().getAppCode(), auditablePrincipalResolver(auditPrincipalIdProvider()), auditTrailExecutionPlan.getAuditTrailManagers(), auditTrailRecordResolutionPlan.getAuditActionResolvers(), auditTrailRecordResolutionPlan.getAuditResourceResolvers()); aspect.setFailOnAuditFailures(!casProperties.getAudit().isIgnoreAuditFailures()); return aspect; } @Autowired @ConditionalOnMissingBean(name = "auditTrailRecordResolutionPlan") @Bean public AuditTrailRecordResolutionPlan auditTrailRecordResolutionPlan(final Listconfigurers) { final DefaultAuditTrailRecordResolutionPlan plan = new DefaultAuditTrailRecordResolutionPlan(); configurers.forEach(c -> { final String name = StringUtils.removePattern(c.getClass().getSimpleName(), "\\$.+"); LOGGER.debug("Registering audit trail manager [{}]", name); c.configureAuditTrailRecordResolutionPlan(plan); }); return plan; } @Autowired @ConditionalOnMissingBean(name = "auditTrailExecutionPlan") @Bean public AuditTrailExecutionPlan auditTrailExecutionPlan(final List configurers) { final DefaultAuditTrailExecutionPlan plan = new DefaultAuditTrailExecutionPlan(); configurers.forEach(c -> { final String name = StringUtils.removePattern(c.getClass().getSimpleName(), "\\$.+"); LOGGER.debug("Registering audit trail manager [{}]", name); c.configureAuditTrailExecutionPlan(plan); }); return plan; } @Bean public FilterRegistrationBean casClientInfoLoggingFilter() { final AuditProperties audit = casProperties.getAudit(); final FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new ClientInfoThreadLocalFilter()); bean.setUrlPatterns(CollectionUtils.wrap("/*")); bean.setName("CAS Client Info Logging Filter"); bean.setAsyncSupported(true); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); final Map initParams = new HashMap<>(); if (StringUtils.isNotBlank(audit.getAlternateClientAddrHeaderName())) { initParams.put(ClientInfoThreadLocalFilter.CONST_IP_ADDRESS_HEADER, audit.getAlternateClientAddrHeaderName()); } if (StringUtils.isNotBlank(audit.getAlternateServerAddrHeaderName())) { initParams.put(ClientInfoThreadLocalFilter.CONST_SERVER_IP_ADDRESS_HEADER, audit.getAlternateServerAddrHeaderName()); } initParams.put(ClientInfoThreadLocalFilter.CONST_USE_SERVER_HOST_ADDRESS, String.valueOf(audit.isUseServerHostAddress())); bean.setInitParameters(initParams); return bean; } @ConditionalOnMissingBean(name = "deleteServiceActionResolver") @Bean public AuditActionResolver deleteServiceActionResolver() { return new DeleteServiceActionResolver(); } @ConditionalOnMissingBean(name = "deleteServiceResourceResolver") @Bean public AuditResourceResolver deleteServiceResourceResolver() { return new DeleteServiceResourceResolver(); } @ConditionalOnMissingBean(name = "authenticationActionResolver") @Bean public AuditActionResolver authenticationActionResolver() { return new DefaultAuditActionResolver("_SUCCESS", AUDIT_ACTION_SUFFIX_FAILED); } @ConditionalOnMissingBean(name = "ticketCreationActionResolver") @Bean public AuditActionResolver ticketCreationActionResolver() { return new DefaultAuditActionResolver("_CREATED", "_NOT_CREATED"); } @ConditionalOnMissingBean(name = "ticketValidationActionResolver") @Bean public AuditActionResolver ticketValidationActionResolver() { return new DefaultAuditActionResolver("D", AUDIT_ACTION_SUFFIX_FAILED); } @ConditionalOnMissingBean(name = "returnValueResourceResolver") @Bean public AuditResourceResolver returnValueResourceResolver() { return new ShortenedReturnValueAsStringResourceResolver(); } @ConditionalOnMissingBean(name = "nullableReturnValueResourceResolver") @Bean public AuditResourceResolver nullableReturnValueResourceResolver() { return new NullableReturnValueAuditResourceResolver(returnValueResourceResolver()); } @ConditionalOnMissingBean(name = "serviceAccessEnforcementAuditResourceResolver") @Bean public ServiceAccessEnforcementAuditResourceResolver serviceAccessEnforcementAuditResourceResolver() { return new ServiceAccessEnforcementAuditResourceResolver(); } /** * Extension point for deployers to define custom AuditActionResolvers to extend the stock resolvers. * * @return the map */ @ConditionalOnMissingBean(name = "customAuditActionResolverMap") @Bean public Map customAuditActionResolverMap() { return new HashMap<>(0); } /** * Extension point for deployers to define custom AuditResourceResolvers to extend the stock resolvers. * * @return the map */ @ConditionalOnMissingBean(name = "customAuditResourceResolverMap") @Bean public Map customAuditResourceResolverMap() { return new HashMap<>(0); } @ConditionalOnMissingBean(name = "auditablePrincipalResolver") @Bean public PrincipalResolver auditablePrincipalResolver(@Qualifier("auditPrincipalIdProvider") final AuditPrincipalIdProvider auditPrincipalIdProvider) { return new ThreadLocalPrincipalResolver(auditPrincipalIdProvider); } @ConditionalOnMissingBean(name = "ticketResourceResolver") @Bean public AuditResourceResolver ticketResourceResolver() { return new TicketAsFirstParameterResourceResolver(); } @ConditionalOnMissingBean(name = "ticketValidationResourceResolver") @Bean public AuditResourceResolver ticketValidationResourceResolver() { final AuditProperties audit = casProperties.getAudit(); if (audit.isIncludeValidationAssertion()) { return new TicketValidationResourceResolver(); } return ticketResourceResolver(); } @ConditionalOnMissingBean(name = "messageBundleAwareResourceResolver") @Bean public AuditResourceResolver messageBundleAwareResourceResolver() { return new MessageBundleAwareResourceResolver(applicationContext); } @ConditionalOnMissingBean(name = "auditPrincipalIdProvider") @Bean public AuditPrincipalIdProvider auditPrincipalIdProvider() { final Map resolvers = applicationContext.getBeansOfType(AuditPrincipalIdProvider.class, false, true); final List providers = new ArrayList<>(resolvers.values()); AnnotationAwareOrderComparator.sort(providers); return new ChainingAuditPrincipalIdProvider(providers); } @Override public void configureAuditTrailExecutionPlan(final AuditTrailExecutionPlan plan) { final AuditSlf4jLogProperties audit = casProperties.getAudit().getSlf4j(); final Slf4jLoggingAuditTrailManager slf4j = new Slf4jLoggingAuditTrailManager(); slf4j.setUseSingleLine(audit.isUseSingleLine()); slf4j.setEntrySeparator(audit.getSinglelineSeparator()); slf4j.setAuditFormat(audit.getAuditFormat()); plan.registerAuditTrailManager(slf4j); } @Override public void configureAuditTrailRecordResolutionPlan(final AuditTrailRecordResolutionPlan plan) { final AuditActionResolver deleteServiceActionResolver = deleteServiceActionResolver(); plan.registerAuditActionResolver("DELETE_SERVICE_ACTION_RESOLVER",deleteServiceActionResolver); final AuditResourceResolver deleteServiceResourceResolver = deleteServiceResourceResolver(); plan.registerAuditResourceResolver("DELETE_SERVICE_RESOURCE_RESOLVER",deleteServiceResourceResolver); /* Add audit action resolvers here. */ final AuditActionResolver resolver = authenticationActionResolver(); plan.registerAuditActionResolver("AUTHENTICATION_RESOLVER", resolver); plan.registerAuditActionResolver("SAVE_SERVICE_ACTION_RESOLVER", resolver); final AuditActionResolver defResolver = new DefaultAuditActionResolver(); plan.registerAuditActionResolver("DESTROY_TICKET_GRANTING_TICKET_RESOLVER", defResolver); plan.registerAuditActionResolver("DESTROY_PROXY_GRANTING_TICKET_RESOLVER", defResolver); final AuditActionResolver cResolver = ticketCreationActionResolver(); plan.registerAuditActionResolver("CREATE_PROXY_GRANTING_TICKET_RESOLVER", cResolver); plan.registerAuditActionResolver("GRANT_SERVICE_TICKET_RESOLVER", cResolver); plan.registerAuditActionResolver("GRANT_PROXY_TICKET_RESOLVER", cResolver); plan.registerAuditActionResolver("CREATE_TICKET_GRANTING_TICKET_RESOLVER", cResolver); final AuditActionResolver authResolver = new DefaultAuditActionResolver("_TRIGGERED", StringUtils.EMPTY); plan.registerAuditActionResolver("AUTHENTICATION_EVENT_ACTION_RESOLVER", authResolver); plan.registerAuditActionResolver("VALIDATE_SERVICE_TICKET_RESOLVER", ticketValidationActionResolver()); final AuditActionResolver serviceAccessResolver = new DefaultAuditActionResolver("_TRIGGERED", StringUtils.EMPTY); plan.registerAuditActionResolver("SERVICE_ACCESS_ENFORCEMENT_ACTION_RESOLVER", serviceAccessResolver); /* Add audit resource resolvers here. */ plan.registerAuditResourceResolver("AUTHENTICATION_RESOURCE_RESOLVER", new CredentialsAsFirstParameterResourceResolver()); final AuditResourceResolver messageBundleAwareResourceResolver = messageBundleAwareResourceResolver(); plan.registerAuditResourceResolver("CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER", messageBundleAwareResourceResolver); plan.registerAuditResourceResolver("CREATE_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER", messageBundleAwareResourceResolver); final AuditResourceResolver ticketResourceResolver = ticketResourceResolver(); plan.registerAuditResourceResolver("DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER", ticketResourceResolver); plan.registerAuditResourceResolver("DESTROY_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER", ticketResourceResolver); plan.registerAuditResourceResolver("GRANT_SERVICE_TICKET_RESOURCE_RESOLVER", new ServiceResourceResolver()); plan.registerAuditResourceResolver("GRANT_PROXY_TICKET_RESOURCE_RESOLVER", new ServiceResourceResolver()); plan.registerAuditResourceResolver("VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER", ticketValidationResourceResolver()); plan.registerAuditResourceResolver("SAVE_SERVICE_RESOURCE_RESOLVER", returnValueResourceResolver()); plan.registerAuditResourceResolver("AUTHENTICATION_EVENT_RESOURCE_RESOLVER", nullableReturnValueResourceResolver()); plan.registerAuditResourceResolver("SERVICE_ACCESS_ENFORCEMENT_RESOURCE_RESOLVER", serviceAccessEnforcementAuditResourceResolver()); /* Add custom resolvers here. */ plan.registerAuditActionResolvers(customAuditActionResolverMap()); plan.registerAuditResourceResolvers(customAuditResourceResolverMap()); }}
package org.apereo.cas.audit.spi.config;import org.apereo.inspektr.audit.annotation.Audit;import org.apereo.inspektr.audit.spi.support.AbstractSuffixAwareAuditActionResolver;import org.aspectj.lang.JoinPoint;/** * @author: wangsaichao * @date: 2018/8/10 * @description: */public class DeleteServiceActionResolver extends AbstractSuffixAwareAuditActionResolver { /** * Constructs the resolver with empty values for the two suffixes. */ public DeleteServiceActionResolver() { this("",""); } /** * Constructs the {@link DeleteServiceActionResolver} with a success suffix and failure * suffix. CANNOT be NULL. * @param successSuffix the suffix to use in the event of a success. * @param failureSuffix the suffix to use in the event of a failure. */ public DeleteServiceActionResolver(final String successSuffix, final String failureSuffix) { super(successSuffix, failureSuffix); } @Override public String resolveFrom(final JoinPoint auditableTarget, final Object retval, final Audit audit) { return audit.action() + getSuccessSuffix(); } @Override public String resolveFrom(final JoinPoint auditableTarget, final Exception exception, final Audit audit) { return audit.action() + getFailureSuffix(); }}
package org.apereo.cas.audit.spi.config;import org.apereo.inspektr.audit.spi.AuditResourceResolver;import org.aspectj.lang.JoinPoint;/** * @author: wangsaichao * @date: 2018/8/10 * @description: */public class DeleteServiceResourceResolver implements AuditResourceResolver { @Override public String[] resolveFrom(JoinPoint target, Object returnValue) { return new String[0]; } @Override public String[] resolveFrom(JoinPoint target, Exception exception) { return new String[0]; }}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.apereo.cas.audit.spi.config.CasCoreAuditConfiguration
因为我们配置的是app1.cas.com.启动cas服务端,启动两个客户端,这时访问app1.cas.com是可以登录成功的,使用app2.cas.com登录时未授权的服务.动态添加service之后,就可以了,也可以删除。删除之后,已登录的服务不会强制退出,退出后重新登录,就不能登录了,演示结果如下: