准备前参考:
v自定义登录页和登录认证
v准备工作
1. cas server下载之后解压,在项目根目录下执行 mvn clean 和 mvn install命令(如果使用给定的命令报错)。
2. 根目录下会生成target目录,target中会有一个cas.war的文件,将这个文件通过 mvn install 发布到maven的本地仓库中。
mvn install:install-file -DgroupId=com.hjz -DartifactId=cas -Dversion=1.0.0 -Dfile=cas.war -Dpackaging=war -DgeneratePom=true
3.项目中的依赖
<dependency> <groupId>com.hjz</groupId> <artifactId>cas</artifactId> <version>1.0.0</version> <type>war</type> </dependency>
v客户端cas ticket验证走查
1.如果请求中携带了参数ticket则将会由TicketValidationFilter来对携带的ticket进行校验。TicketValidationFilter只是对验证ticket的这一类Filter的统称,其并不对应Cas Client中的一个具体类型。Cas Client中有多种验证ticket的Filter,都继承自AbstractTicketValidationFilter,它们的验证逻辑都是一致的,都有AbstractTicketValidationFilter实现,所不同的是使用的TicketValidator不一样。笔者这里将以Cas20TicketValidationFilter为例。
<context-param> <param-name>serverName</param-name> <param-value>http://127.0.0.1:8089</param-value> </context-param> <filter> <filter-name>casTicketValidationFilter</filter-name> <filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>http://127.0.0.1:8080/cas</param-value> </init-param> </filter> <filter-mapping> <filter-name>casTicketValidationFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
一个是用来指定Cas Server登录地址的casServerLoginUrl,另一个是用来指定认证成功后需要跳转地址的serverName或service。service和serverName只需要指定一个就可以了。当两者都指定了,参数service将具有更高的优先级,即将以service指定的参数值为准。service和serverName的区别在于service指定的是一个确定的URL,认证成功后就会确切的跳转到service指定的URL;而serverName则是用来指定主机名,其格式为{protocol}:{hostName}:{port},如:https://localhost:8443,当指定的是serverName时,AuthenticationFilter将会把它附加上当前请求的URI,以及对应的查询参数来构造一个确定的URL,如指定serverName为“http://localhost”,而当前请求的URI为“/app”,查询参数为“a=b&b=c”,则对应认证成功后的跳转地址将为“http://localhost/app?a=b&b=c”。
必须指定的参数:
casServerUrlPrefix:用来指定Cas Server对应URL地址的前缀,如上面示例的“https://elim:8443/cas”。
serverName或service:语义跟前面介绍的一致。
可选参数:
redirectAfterValidation :表示是否验证通过后重新跳转到该URL,但是不带参数ticket,默认为true。
useSession :在验证ticket成功后会生成一个Assertion对象,如果useSession为true,则会将该对象存放到Session中。如果为false,则要求每次请求都需要携带ticket进行验证,显然useSession为false跟redirectAfterValidation为true是冲突的。默认为true。
exceptionOnValidationFailure :表示ticket验证失败后是否需要抛出异常,默认为true。
renew:当值为true时将发送“renew=true”到Cas Server,默认为false。
2.通过模仿 Cas10TicketValidationFilter 的逻辑,自定义 ticket validation filter。
这样,servername 和 casServerUrlPrefix 获取的逻辑可以自定义了,例如可以放到 .properties 中。
3.通过casServerUrlPrefix 和 Cas10TicketValidator 或(Cas20ProxyTicketValidator)的 getUrlSuffix方法(返回值分别是“validate”和“serviceValidate”)可以确定出 具体的校验ticket的接口。通过查看 cas server的web.xml,可以在servlet-mapping中看到getUrlSuffix返回值对应的servlet是org.springframework.web.servlet.DispatcherServlet,参数contextConfigLocation 对应的值是'/WEB-INF/cas-servlet.xml, /WEB-INF/cas-servlet-*.xml',里面定义了springmvc初始的handlerMapping和handlerAdapter,如下。
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" p:alwaysUseFullPath="true"> <property name="mappings"> <util:properties> <prop key="/serviceValidate">serviceValidateController</prop> <prop key="/proxyValidate">proxyValidateController</prop> <prop key="/p3/serviceValidate">v3ServiceValidateController</prop> <prop key="/p3/proxyValidate">v3ProxyValidateController</prop> <prop key="/validate">legacyValidateController</prop> <prop key="/proxy">proxyController</prop> <prop key="/authorizationFailure.html">passThroughController</prop> </util:properties> </property> </bean>
找到 '/serviceValidate' 对应的bean -> serviceValidateController
<bean id="abstractValidateController" class="org.jasig.cas.web.ServiceValidateController" abstract="true" p:centralAuthenticationService-ref="centralAuthenticationService" p:proxyHandler-ref="proxy20Handler" p:argumentExtractor-ref="casArgumentExtractor" p:servicesManager-ref="servicesManager" /> <bean id="serviceValidateController" parent="abstractValidateController" p:validationSpecificationClass="org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification"/>
于是可以确认 最终调用 org.jasig.cas.web.ServiceValidateController 进行ticket验证。到底是调用哪个方法呢? 这里先看一下 springmvc中 DispatchServlet的doDispatch方法,如下。
接下来看一下 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter 的handle方法,如下。
可见,handlerAdapter的handle方法调用了某个具体handler的handleRequest方法,查看如上handler配置中 org.jasig.cas.web.ServiceValidateController 的handleRequest方法,如下。
父类handleRequest方法调用了handleRequestInternal方法,子类ServiceValidateController 自身重写了 handleRequestInternal方法。
handleRequestInternal方法中调用了一个argumentExtractor,在上面的配置中可以看到,可确定这是个 spring bean。这个bean在 WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml中,内容如下。
查看org.jasig.cas.web.support.CasArgumentExtractor内容如下。
最后查看org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl内容如下。
在SimpleWebApplicationServiceImpl createServiceFrom方法中可以看到,ticket从request中取到。也就是对应 org.jasig.cas.web.ServiceValidateController 的handleRequestInternal 如下获取方式。
final WebApplicationService service = this.argumentExtractor.extractService(request); final String serviceTicketId = service != null ? service.getArtifactId() : null;
v通过配置deployerConfigContext.xml实现自定义认证(本例中提供了四中方式)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:sec="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 数据源配置, 使用druid连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${druid.initialSize}"/> <property name="minIdle" value="${druid.minIdle}"/> <property name="maxActive" value="${druid.maxActive}"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="${druid.maxWait}"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${druid.validationQuery}" /> <property name="testWhileIdle" value="${druid.testWhileIdle}" /> <property name="testOnBorrow" value="${druid.testOnBorrow}" /> <property name="testOnReturn" value="${druid.testOnReturn}" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。--> <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" /> </bean> <bean id="usernamePasswordAuthenticationHandler" class="com.hjzgg.auth.UsernamePasswordAuthenticationHandler"/> <util:map id="authenticationHandlersResolvers"> <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" /> <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" /> </util:map> <util:list id="authenticationMetadataPopulators"> <ref bean="successfulHandlerMetaDataPopulator" /> <ref bean="rememberMeAuthenticationMetaDataPopulator" /> </util:list> <bean id="attributeRepository" class="org.jasig.services.persondir.support.NamedStubPersonAttributeDao" p:backingMap-ref="attrRepoBackingMap" /> <!--first 默认方式--> <!--<alias name="acceptUsersAuthenticationHandler" alias="primaryAuthenticationHandler" />--> <!--second 使用 MD5对密码进行加密--> <!--<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" autowire="byName"> <constructor-arg value="MD5"/> </bean > <bean id="queryDatabaseAuthenticationHandler" name="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"> <property name="passwordEncoder" ref="MD5PasswordEncoder"/> </bean>--> <!--third 不使用加密算法--> <!--<alias name="queryDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />--> <!--forth 通过继承AbstractUsernamePasswordAuthenticationHandler,并实现authenticateUsernamePasswordInternal--> <alias name="usernamePasswordAuthenticationHandler" alias="primaryAuthenticationHandler" /> <alias name="personDirectoryPrincipalResolver" alias="primaryPrincipalResolver" /> <alias name="dataSource" alias="queryDatabaseDataSource" /> <util:map id="attrRepoBackingMap"> <entry key="uid" value="uid" /> <entry key="eduPersonAffiliation" value="eduPersonAffiliation" /> <entry key="groupMembership" value="groupMembership" /> <entry> <key><value>memberOf</value></key> <list> <value>faculty</value> <value>staff</value> <value>org</value> </list> </entry> </util:map> <alias name="serviceThemeResolver" alias="themeResolver" /> <alias name="jsonServiceRegistryDao" alias="serviceRegistryDao" /> <alias name="defaultTicketRegistry" alias="ticketRegistry" /> <alias name="ticketGrantingTicketExpirationPolicy" alias="grantingTicketExpirationPolicy" /> <alias name="multiTimeUseOrTimeoutExpirationPolicy" alias="serviceTicketExpirationPolicy" /> <alias name="anyAuthenticationPolicy" alias="authenticationPolicy" /> <alias name="acceptAnyAuthenticationPolicyFactory" alias="authenticationPolicyFactory" /> <bean id="auditTrailManager" class="org.jasig.inspektr.audit.support.Slf4jLoggingAuditTrailManager" p:entrySeparator="${cas.audit.singleline.separator:|}" p:useSingleLine="${cas.audit.singleline:false}"/> <alias name="neverThrottle" alias="authenticationThrottle" /> <util:list id="monitorsList"> <ref bean="memoryMonitor" /> <ref bean="sessionMonitor" /> </util:list> <alias name="defaultPrincipalFactory" alias="principalFactory" /> <alias name="defaultAuthenticationTransactionManager" alias="authenticationTransactionManager" /> <alias name="defaultPrincipalElectionStrategy" alias="principalElectionStrategy" /> <alias name="tgcCipherExecutor" alias="defaultCookieCipherExecutor" /> </beans>
v相关参考
vCAS Properties
vDatabase Authentication
vCAS4.2.x
v报异常:Application Not Authorized to Use CAS
vCas https 证书生成
keytool -genkey -keyalg RSA -alias cas.server.com -dname "cn=cas.server.com" -keystore /usr/data/tomcat.keystore
sudo keytool -export -alias cas.server.com -keystore /usr/data/tomcat.keystore -file /usr/data/tomcat.cer
cd /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/security
sudo keytool -import -keystore cacerts -file /usr/data/tomcat.cer -alias cas.server.com -storepass changeit
sudo keytool -delete -trustcacerts -alias cas.server.com -keystore "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/security/cacerts" -storepass changeit
keytool -list -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/security/cacerts -storepass changeit
完整项目地址:https://github.com/hjzgg/cas4.2.7-authentication
cas4.2.7 template项目地址:https://github.com/hjzgg/cas-overlay-template-4.2.7