【原创】apereo cas 4.2从0开始

转载请声明来自:http://www.cnblogs.com/chixinzei/p/7504272.html   谢谢!

目前已实现功能

数据库验证,druid数据源,页面定制,restful登录,shibboleth idp saml2.0整合,根据账号自动登录服务器后跳转子系统链接(webUtils修改的有问题,暂时不要参照此功能)

 

 

cas相关参考文档

http://www.cnblogs.com/xwdreamer/archive/2011/11/10/2296939.html spring webflow

https://apereo.github.io/cas/4.2.x/ CAS官方文档

http://docs.spring.io/spring-webflow/docs/2.4.5.RELEASE/reference/html Spring webflow 官方文档

与Shibboleth IDP端 SAML2.0整合文档:

https://apereo.github.io/cas/4.2.x/integration/Shibboleth.html 官网cas和Shibboleth IDP 整合

http://wwwcomy.iteye.com/blog/2236016 Shibboleth IDP安装中文博客

https://wiki.shibboleth.net/confluence/display/SHIB2/IdPInstall Shibboleth IDP安装


工作环境准备

运行基础:jdk8.0.131,cas-server:4.2.1(基于gradle),cas-client:3.3.3(基于maven),win7 64,tomcat8.5,mysql 5.6.21,idea-2017.2

ssl配置相关准备

1.http访问配置

取消cas https验证:http://www.cnblogs.com/xiaojf/p/6617693.html

2.https访问配置

准备本地tomcat证书生成与配置:文档参考:http://www.bug315.com/article/412.htm

若要在本地使用SSL访问cas,则只需要修改cas-server-webapp\src\main\resources\services\HTTPSandIMAPS-10000001.json的属性tgc.secure=false即可,意思是cas的cookie是否只在ssl下生成(如果为true,则登陆后cookie也不会存放登录信息和票据,依然重定向到登录界面)

默认jdk密匙库口令:changeit

 

1)cmd 移动到tomcat目录下,生成server key :

keytool -genkey -alias tomcat -keyalg RSA -storepass changeit -keystore server.keystore -validity 3600

解释:

-alias 表示证书的别名,一个keystore文件中可以存放多个alias。

-keyalg RSA 表示密钥算法的名称为RSA算法

-keypass changeit 表示密钥的口令是changeit

-storepass changeit 表示密钥库(生成的keystore文件)的密钥是changeit

-keystore server.keystore 表示指定密匙库的名称

-validity 3600  表示证书有效期3600天,也就是大概10年

 

删除证书命令:

先移动到jdk的密匙总库目录:

如:D:\Program Files\Java\jdk1.8.0_131\jre\lib\security

执行删除jdk密匙库证书:

keytool -delete -alias tomcat -keystore cacerts

删除后可以再次将证书导入jdk密匙库

 

2)输入参数:

名字和姓氏:149p874e84.51mypc.cn (域名)

组织单位名称:149p874e84.51mypc.cn

组织名称:149p874e84.51mypc.cn

所在城市:jinjiang

所在省:fujian

国家/地区代码:zh

 

3)证书导入的JDK的证书信任库中

keytool -export -trustcacerts -alias tomcat -file server.cer -keystore  server.keystore -storepass changeit 

 keytool -import -trustcacerts -alias tomcat -file server.cer -keystore  "jdk目录/jre/lib/security/cacerts" -storepass changeit

4)修改tomcat server.xml增加https支持,不需要删除原来的http连接

<!--开启https连接,如果是非外部ide启动,则keystoreFile配置为server.keystore即可!--> 

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" 

maxThreads="150" scheme="https" secure="true" 

clientAuth="false" sslProtocol="TLS" 

keystoreFile="D:/Program Files (x86)/apache-tomcat-8.5.16(8086)/server.keystore" keystorePass="changeit"/>

3.支持配置多个验证拦截器。

简单用户名和密码配置修改:/cas-server-support-x509/src/test/resources/deployerConfigContext.xml

casuser,Mellon。

clipboard(5)

另外此用户名密码也可以配置在cas.properties中:

clipboard(7)

两者都存在时优先使用properties的配置

xml存在,而properties不存在时,则读取xml。


CAS服务端配置修改

⒈jdbc简单查询校验:

 

1.1mysql建表:

CREATE TABLE `t_user` ( 

`id` bigint(15) NOT NULL COMMENT'主键', 

`account` varchar(30) DEFAULT NULL COMMENT'账号', 

`password` varchar(255) DEFAULT NULL COMMENT'密码', 

`valid` tinyint(1) DEFAULT NULL COMMENT '是否有效', 

PRIMARY KEY(`id`) 

) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

--插入1条数据: 

INSERT INTO `t_user` 

(`id`, 

`account`, 

`password`, 

`valid`) 

VALUES 

(1, 

'admin', 

'md5加密后的密码', 

1);

 

1.2 cas-server-webapp\build.gradle 引入jdbc支持包依赖:

compile project(':cas-server-support-jdbc') 

compile group: 'mysql', name: 'mysql-connector-java', version: mysqlConnectorJavaVersion

 

 

1.3 deployerConfigContext.xml

关闭简单用户密码校验,改为jdbc校验,密码MD5加密,配合druid

 1 <!--修改登录方式为jdbc验证,注意,需要注释掉QueryAndEncodeDatabaseAuthenticationHandler的注解注入,有冲突-->
 2 <!--<alias name="acceptUsersAuthenticationHandler" alias="primaryAuthenticationHandler" />-->
 3 <alias name="queryDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler"/>
 4 <bean id="queryDatabaseAuthenticationHandler"
 5           class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
 6     <!--<property name="dataSource" ref="queryDatabaseDataSource"></property>-->
 7     <!--<property name="sql" value="select password from t_user where account=?"></property>-->
 8     <property name="passwordEncoder" ref="MD5PasswordEncoder"></property>
 9 </bean>
10 <!-- 添加MD5密码加密功能 -->
11 <bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
12     <constructor-arg index="0">
13         <value>MD5</value>
14     </constructor-arg>
15 </bean>
16 
17 <!-- 数据源配置 -->
18 <alias name="dataSource" alias="queryDatabaseDataSource"/>
19 <!-- 阿里 druid 数据库连接池 -->
20 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
21     <!-- 数据库基本信息配置 -->
22     <property name="url" value="${cas.druid.database.url}"/>
23     <property name="username" value="${cas.druid.database.username}"/>
24     <property name="password" value="${cas.druid.database.password}"/>
25     <property name="driverClassName" value="${cas.druid.database.driverClassName}"/>
26     <property name="filters" value="${cas.druid.database.filters}"/>
27     <!-- 最大并发连接数 -->
28     <property name="maxActive" value="${cas.druid.database.maxActive}"/>
29     <!-- 初始化连接数量 -->
30     <property name="initialSize" value="${cas.druid.database.initialSize}"/>
31     <!-- 配置获取连接等待超时的时间 -->
32     <property name="maxWait" value="${cas.druid.database.maxWait}"/>
33     <!-- 最小空闲连接数 -->
34     <property name="minIdle" value="${cas.druid.database.minIdle}"/>
35     <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
36     <property name="timeBetweenEvictionRunsMillis" value="${cas.druid.database.timeBetweenEvictionRunsMillis}"/>
37     <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
38     <property name="minEvictableIdleTimeMillis" value="${cas.druid.database.minEvictableIdleTimeMillis}"/>
39     <property name="validationQuery" value="${cas.druid.database.validationQuery}"/>
40     <property name="testWhileIdle" value="${cas.druid.database.testWhileIdle}"/>
41     <property name="testOnBorrow" value="${cas.druid.database.testOnBorrow}"/>
42     <property name="testOnReturn" value="${cas.druid.database.testOnReturn}"/>
43     <property name="maxOpenPreparedStatements" value="${cas.druid.database.maxOpenPreparedStatements}"/>
44     <!-- 打开 removeAbandoned 功能 -->
45     <property name="removeAbandoned" value="${cas.druid.database.removeAbandoned}"/>
46     <!-- 1800 秒,也就是 30 分钟 -->
47     <property name="removeAbandonedTimeout" value="${cas.druid.database.removeAbandonedTimeout}"/>
48     <!-- 关闭 abanded 连接时输出错误日志 -->
49     <property name="logAbandoned" value="${cas.druid.database.logAbandoned}"/>
50     <property name="proxyFilters">
51         <list>
52             <ref bean="log-filter"/>
53         </list>
54     </property>
55 </bean>
56 <bean id="log-filter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
57     <property name="connectionLogEnabled" value="${cas.druid.database.connectionLogEnabled}"/>
58     <property name="statementLogEnabled" value="${cas.druid.database.statementLogEnabled}"/>
59     <property name="resultSetLogEnabled" value="${cas.druid.database.resultSetLogEnabled}"/>
60     <property name="statementExecutableSqlLogEnable" value="${cas.druid.database.statementExecutableSqlLogEnable}"/>
61 </bean>
View Code

 

1.4 cas.propertie中新增配置:

 1 #sql通过用户名查询密码
 2 cas.jdbc.authn.query.sql=select password  from  t_user  where account=? and  valid=true
 3 
 4 #druid config by xbwu
 5 cas.druid.database.url=jdbc:mysql://localhost:3306/xbwudb?characterEncoding=utf8
 6 cas.druid.database.username=root
 7 cas.druid.database.password=root
 8 cas.druid.database.driverClassName=com.mysql.jdbc.Driver
 9 cas.druid.database.filters=stat,wall
10 cas.druid.database.maxActive=20
11 cas.druid.database.initialSize=1
12 cas.druid.database.maxWait=60000
13 cas.druid.database.minIdle=10
14 cas.druid.database.timeBetweenEvictionRunsMillis=60000
15 cas.druid.database.minEvictableIdleTimeMillis=300000
16 cas.druid.database.validationQuery=SELECT 'x'
17 cas.druid.database.testWhileIdle= true
18 cas.druid.database.testOnBorrow=false
19 cas.druid.database.testOnReturn=false
20 cas.druid.database.maxOpenPreparedStatements=20
21 cas.druid.database.removeAbandoned=true
22 cas.druid.database.removeAbandonedTimeout=1800
23 cas.druid.database.logAbandoned=true
24 
25 cas.druid.database.connectionLogEnabled=false
26 cas.druid.database.statementLogEnabled=false
27 cas.druid.database.resultSetLogEnabled=true
28 cas.druid.database.statementExecutableSqlLogEnable=true
View Code

 

1.5 web.xml新增druid控制台:

 1 <!-- 连接池 启用 Web 监控统计功能    start-->
 2 <filter>
 3     <filter-name> DruidWebStatFilter </filter-name>
 4     <filter-class> com.alibaba.druid.support.http.WebStatFilter </filter-class>
 5     <init-param >
 6         <param-name> exclusions </param-name>
 7         <param-value> *. js ,*. gif ,*. jpg ,*. png ,*. css ,*. ico ,/ druid /* </param-value>
 8     </init-param>
 9 </filter >
10 <filter-mapping>
11     <filter-name> DruidWebStatFilter </filter-name>
12     <url-pattern>/*</url-pattern>
13 </filter-mapping>
14 <servlet >
15     <servlet-name> DruidStatView </servlet-name>
16     <servlet-class> com.alibaba.druid.support.http.StatViewServlet </servlet-class>
17 </servlet >
18 <servlet-mapping>
19     <servlet-name> DruidStatView </servlet-name>
20     <url-pattern>/druid/console/*</url-pattern>
21 </servlet-mapping>
22 <!-- 连接池 启用 Web 监控统计功能    end—>
View Code

 

1.6 cas-server-webapp\src\main\resources\log4j2.xml新增druid日志配置

  1 <?xml version="1.0" encoding="UTF-8" ?>
  2 <!-- Specify the refresh internal in seconds. -->
  3 <!-- OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
  4 <Configuration monitorInterval="60">
  5     <Appenders>
  6         <Console name="console" target="SYSTEM_OUT">
  7             <PatternLayout pattern="%d %p [%c] - %n&lt;%m&gt;%n"/>
  8         </Console>
  9         <RollingFile name="file" fileName="../caslog/cas.log" append="true"
 10                      filePattern="../caslog/cas-%d{yyyy-MM-dd-HH}.log">
 11             <PatternLayout pattern="%d %p [%c] - %n&lt;%m&gt;%n"/>
 12             <Policies>
 13                 <OnStartupTriggeringPolicy />
 14                 <SizeBasedTriggeringPolicy size="10 MB"/>
 15                 <TimeBasedTriggeringPolicy />
 16             </Policies>
 17         </RollingFile>
 18         <RollingFile name="auditlogfile" fileName="../caslog/cas_audit.log" append="true"
 19                      filePattern="../caslog/cas_audit-%d{yyyy-MM-dd-HH}.log">
 20             <PatternLayout pattern="%d %p [%c] - %m%n"/>
 21             <Policies>
 22                 <OnStartupTriggeringPolicy />
 23                 <SizeBasedTriggeringPolicy size="10 MB"/>
 24                 <TimeBasedTriggeringPolicy />
 25             </Policies>
 26         </RollingFile>
 27         <RollingFile name="perfFileAppender" fileName="../caslog/perfStats.log" append="true"
 28                      filePattern="../caslog/perfStats-%d{yyyy-MM-dd-HH}.log">
 29             <PatternLayout pattern="%m%n"/>
 30             <Policies>
 31                 <OnStartupTriggeringPolicy />
 32                 <SizeBasedTriggeringPolicy size="10 MB"/>
 33                 <TimeBasedTriggeringPolicy />
 34             </Policies>
 35         </RollingFile>
 36     </Appenders>
 37     <Loggers>
 38         <AsyncLogger  name="org.jasig" level="debug" additivity="false" includeLocation="true">
 39             <AppenderRef ref="console"/>
 40             <AppenderRef ref="file"/>
 41         </AsyncLogger>
 42         <!--不配置append默认使用AsyncRoot的append-->
 43         <AsyncLogger  name="org.springframework" level="warn" />
 44         <AsyncLogger name="org.springframework.webflow" level="warn" />
 45         <AsyncLogger name="org.springframework.web" level="warn" />
 46         <AsyncLogger name="org.pac4j" level="warn" />
 47         <!--
 48         <AsyncLogger name="org.opensaml" level="debug" additivity="false">
 49             <AppenderRef ref="console"/>
 50             <AppenderRef ref="file"/>
 51         </AsyncLogger>
 52         <AsyncLogger name="org.ldaptive" level="debug" additivity="false">
 53             <AppenderRef ref="console"/>
 54             <AppenderRef ref="file"/>
 55         </AsyncLogger>
 56         <AsyncLogger name="com.hazelcast" level="debug" additivity="false">
 57             <AppenderRef ref="console"/>
 58             <AppenderRef ref="file"/>
 59         </AsyncLogger>
 60         -->
 61 
 62         <AsyncLogger name="perfStatsLogger" level="info" additivity="false" includeLocation="true">
 63             <AppenderRef ref="perfFileAppender"/>
 64         </AsyncLogger>
 65 
 66         <AsyncLogger name="org.jasig.cas.web.flow" level="info" additivity="true" includeLocation="true">
 67             <AppenderRef ref="file"/>
 68         </AsyncLogger>
 69         <AsyncLogger name="org.jasig.inspektr.audit.support" level="info" includeLocation="true">
 70             <AppenderRef ref="console"/>
 71             <AppenderRef ref="auditlogfile"/>
 72             <AppenderRef ref="file"/>
 73         </AsyncLogger>
 74         <!--druid连接池信息打印-->
 75         <AsyncLogger name="druid.sql.Statement" level="debug" additivity="false" includeLocation="true">
 76             <AppenderRef ref="console"/>
 77             <AppenderRef ref="file"/>
 78         </AsyncLogger>
 79         <!--<AsyncLogger name="druid.sql.DataSource" level="debug" additivity="false" includeLocation="true">
 80             <AppenderRef ref="console"/>
 81         </AsyncLogger>
 82         <AsyncLogger name="druid.sql.Connection" level="debug" additivity="false" includeLocation="true">
 83             <AppenderRef ref="console"/>
 84         </AsyncLogger>-->
 85         <!--<AsyncLogger name="druid.sql.ResultSet" level="debug" additivity="false" includeLocation="true">
 86             <AppenderRef ref="console"/>
 87         </AsyncLogger>-->
 88         <AsyncLogger name="druid.sql" level="error" additivity="false" includeLocation="true">
 89             <AppenderRef ref="console"/>
 90         </AsyncLogger>
 91         <!--根输出配置,输出等级-->
 92         <!-- OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
 93         <!--如果上面的日志输出配置了appender,并且输出等级跟根配置有重叠-->
 94         <!--比如上面配置debug,则两者的info级别以上日志都会在相同的输出appender中(console)输出,也就是说控制台会输出2次-->
 95         <!--所以尽量根输出配置高一点,比如error等级-->
 96         <!--如果我们确实有这种需求(不想遵循父类的Appender),可以在自定义配置中加上additivity="false"参数-->
 97         <AsyncRoot level="info">
 98             <AppenderRef ref="console"/>
 99             <AppenderRef ref="file"/>
100         </AsyncRoot>
101     </Loggers>
102 </Configuration>
View Code

 

1.7 页面样式定制化修改配置:

1.7.1 复制cas-server-webapp\src\main\webapp\WEB-INF下的view文件夹一份,命名为:view_custom

clipboard

1.7.2 修改cas-server-webapp\src\main\webapp\WEB-INF\cas.properties内容:

clipboard(1)

1.7.3 样式文件-修改cas-server-webapp\src\main\resources\cas-theme-default.properties的内容:

clipboard(11)

4.复制css和js各一份出来,重命名为上面的名字:

clipboard(6)

 

 

2.服务器支持restfull调用登录

cas-server-webapp\build.gradle 增加依赖:compile project(':cas-server-support-rest')

cas-server-webapp\src\main\webapp\WEB-INF\cas.properties 加长一次性票据有效期:st.timeToKillInSeconds=20 20秒有效

cas单点登录restfull请求登录范例:

参考官方文档:https://apereo.github.io/cas/5.1.x/protocol/REST-Protocol.html

2.1 请求获取TGT

请求方式:POST

head设置:Content-Type: application/x-www-form-urlencoded

范例:{cas服务器地址}/cas/v1/tickets?username=battags&password=password&additionalParam1=paramvalue

demo: http://localhost:8080/cas/v1/tickets?username=admin&password=1234qwer

正确返回:

返回头:

HTTP/1.1 201

Cache-Control: no-store

Location: http://localhost:8080/cas/v1/tickets/TGT-1-fHlelcwDXNYOiFpiiGwnTtyArXD4rjNdtelyBaBaBWWNHHHvav-www.casServer.com

Content-Type: text/html;charset=UTF-8

Content-Length: 376

Date: Mon, 07 Aug 2017 01:42:35 GMT

2.2 拿到TGT,请求一次性票据ST

请求方式:POST

head设置:Content-Type: application/x-www-form-urlencoded

范例:{cas服务器地址}/cas/v1/tickets/{TGT id}?service={form encoded parameter for the service url}

demo:http://localhost:8080/cas/v1/tickets/TGT-1-fHlelcwDXNYOiFpiiGwnTtyArXD4rjNdtelyBaBaBWWNHHHvav-www.casServer.com?service=http%3A%2F%2FcasTest02.com%3A8082%2F

正确返回:

返回头:

HTTP/1.1 200

Cache-Control: no-store

Content-Disposition: inline;filename=f.txt

Content-Type: application/x-msdownload;charset=UTF-8

Content-Length: 43

Date: Mon, 07 Aug 2017 01:45:56 GMT

返回内容:

ST-1-0wjyNMlwOK3kevi5Fa6w-www.casServer.com

2.3 拿着一次性票据,请求服务器验证

请求方式:GET

范例:{cas服务器地址}/cas/p3/serviceValidate?service={service url}&ticket={service ticket}

demo:http://localhost:8080/cas/p3/serviceValidate?ticket=ST-1-0wjyNMlwOK3kevi5Fa6w-www.casServer.com&service=http%3A%2F%2FcasTest02.com%3A8082%2F

正确返回:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>

<cas:authenticationSuccess>

<cas:user>admin</cas:user>

<cas:attributes>

<cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>

<cas:isFromNewLogin>true</cas:isFromNewLogin>

<cas:authenticationDate>2017-08-07T10:09:02.521+08:00</cas:authenticationDate>

</cas:attributes>

</cas:authenticationSuccess>

</cas:serviceResponse>

一次性票据过期返回:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>

<cas:authenticationFailure code='INVALID_TICKET'>

未能够识别出目标 &#039;ST-1-0wjyNMlwOK3kevi5Fa6w-www.casServer.com&#039;票根

</cas:authenticationFailure>

</cas:serviceResponse>


CAS客户端

1.单点登录拦截器配置:web.xml

<!-- ========================单点登录开始 ======================== -->
    <!--用于单点退出,该过滤器用于实现单点登出功能,可选配置 -->
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>
    <!--该过滤器用于实现单点登出功能,可选配置。 -->
    <filter>
        <filter-name>CASSingle Sign OutFilter</filter-name>
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CASSingle Sign OutFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CASFilter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>http://localhost:8080/cas/login</param-value>
        </init-param>
        <init-param>
            <!--此地址是用于给cas服务器进行回调的域名和端口,注意,如果在这个回调地址后面配置了具体的路径和参数,而非简单的域名端口,比如:-->
            <!--http://casTest01.com:8081/aaa/bb.jsp?AA=123-->
            <!--1.参数AA=123在访问任何客户端地址时都会cas服务器被带回来-->
            <!--2.具体路径:/aaa/bb.jsp   只有在访问客户端简单域名加端口时,cas服务器才会将完整路径返回:http://casTest01.com:8081/aaa/bb-->
            <param-name>serverName</param-name>
            <param-value>http://casTest01.com:8081</param-value>

            <!--此地址是用于给cas服务器进行绝对回调的地址,只要是被cas拦截,只回调地址必然是service所对应参数-->
            <!--<param-name>service</param-name>-->
            <!--<param-value>http://casTest01.com:8081/loginSuccess.do</param-value>-->
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CASFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--该过滤器负责对Ticket的校验工作,必须启用它 -->
    <filter>
        <filter-name>CASValidationFilter</filter-name>
        <filter-class>
            org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
        </filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>http://localhost:8080/cas</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://casTest01.com:8081</param-value>
        </init-param>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CASValidationFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 -->
    <filter>
        <filter-name>CASHttpServletRequest WrapperFilter</filter-name>
        <filter-class>
            org.jasig.cas.client.util.HttpServletRequestWrapperFilter
        </filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CASHttpServletRequest WrapperFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 -->
    <filter>
        <filter-name>CASAssertion Thread LocalFilter</filter-name>
        <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CASAssertion Thread LocalFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- ========================单点登录结束 ======================== -->

  


番外篇!--与shibboleth idp saml2.0 联合配置

1.工作环境准备

1.1 运行基础

jdk8.0.131 tomcat8.5.16 cas-4.2.1(基于gradle) ,shibboleth-identityprovider-2.4.5,win7 64,mysql 5.6.21,花生壳免费域名映射(否则自己需要再下一个shibboleth的sp服务器用于测试)

1.2 ssl配置

tomcat必须配置ssl连接,此tomcat用于启动cas-server和shib-idp(我一个是用idea启动,ssl访问端口设置不同而已),参考上面的《本地tomcat证书生成与配置》

2. 安装shibboleth-identityprovider-2.4.5

参考文档:https://wiki.shibboleth.net/confluence/display/SHIB2/IdPInstall

2.1 添加cas client  包支持

把cas的客户端jar包(cas-client-core-3.4.1.jar)丢到idp解压的lib目录下,例如:D:\Program Files (x86)\shibboleth-identityprovider-2.4.5\lib,这样安装好的idp就会有这个jar包

2.2 执行install.bat安装idp

clipboard(4)

2.3 修改idp的元数据文件

D:\Program Files (x86)\shibboleth-idp-server\metadata\idp-metadata.xml

这里面的几个idp接口访问端口要改一下,因为我用的花生壳所以端口号是指定好了的,把文件里面的:8443全部替换成空串即可。

 注意,由于默认该元数据文件生成的sope是二级域名scope,需要改成完整的三级域名sope,否则testshib验证不通过!

例如:

我的花生壳idp地址:149p874e84.51mypc.cn:11898

原始生成   idp-metadata.xml的scope:<shibmd:Scope regexp="false">51mypc.cn:11898</shibmd:Scope>

需要修改成:<shibmd:Scope regexp="false">149p874e84.51mypc.cn:11898</shibmd:Scope>

 

3.cas-server所需配合部分

上面先告一段落,开始修改cas-server的整合部分:

参考:https://apereo.github.io/cas/4.2.x/integration/Shibboleth.html

3.1  修改idp安装目录配置文件

idp安装目录/conf/handler.xml

把里面的ph:LoginHandler xsi:type="ph:RemoteUser"节点替换成以下内容:

<!-- Remote User handler for CAS support -->
    <ph:LoginHandler xsi:type="ph:RemoteUser">
        <ph:AuthenticationMethod>
            urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
          </ph:AuthenticationMethod>
        <ph:AuthenticationMethod>
            urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
          </ph:AuthenticationMethod>
    </ph:LoginHandler>

3.2 修改idp.war包的web.xml文件

 

增加以下内容:

 1 <!-- For CAS client support -->
 2 <context-param>
 3     <param-name>serverName</param-name>
 4     <param-value>${idp域名+端口}</param-value>
 5 </context-param>
 6 <!-- CAS client filters -->
 7 <filter>
 8     <filter-name>CAS Authentication Filter</filter-name>
 9     <filter-class>
10       org.jasig.cas.client.authentication.AuthenticationFilter
11   </filter-class>
12     <init-param>
13         <param-name>casServerLoginUrl</param-name>
14         <param-value>${cas服务器域名加端口加项目名}/login</param-value>
15     </init-param>
16 </filter>
17 <filter-mapping>
18     <filter-name>CAS Authentication Filter</filter-name>
19     <url-pattern>/Authn/RemoteUser</url-pattern>
20 </filter-mapping>
21 <filter>
22     <filter-name>CAS Validation Filter</filter-name>
23     <filter-class>
24     org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
25   </filter-class>
26     <init-param>
27         <param-name>casServerUrlPrefix</param-name>
28         <param-value>${cas服务器域名加端口加项目名}</param-value>
29     </init-param>
30     <init-param>
31         <param-name>redirectAfterValidation</param-name>
32         <param-value>true</param-value>
33     </init-param>
34 </filter>
35 <filter-mapping>
36     <filter-name>CAS Validation Filter</filter-name>
37     <url-pattern>/Authn/RemoteUser</url-pattern>
38 </filter-mapping>
39 <filter>
40     <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
41     <filter-class>
42     org.jasig.cas.client.util.HttpServletRequestWrapperFilter
43   </filter-class>
44 </filter>
45 <filter-mapping>
46     <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
47     <url-pattern>/Authn/RemoteUser</url-pattern>
48 </filter-mapping>
View Code

 

3.3 cas-server增加saml2.0支持包 cas-server-support-saml-mdui

 

3.4 cas-server-webapp的deployerConfigContext.xml添加

 1 <!--saml2.0支持-->
 2 <bean id="samlMetadataUIParserAction"
 3               class="org.jasig.cas.support.saml.web.flow.mdui.SamlMetadataUIParserAction"
 4               c:entityIdParameterName="entityId"
 5               c:metadataAdapter-ref="metadataAdapter"/>
 6 <!--元数据配置-->
 7 <bean id="metadataAdapter"
 8               class="org.jasig.cas.support.saml.web.flow.mdui.StaticMetadataResolverAdapter"
 9               c:metadataResources-ref="metadataResources"
10               p:refreshIntervalInMinutes="300"
11               p:requireValidMetadata="true" />
12 <!--元数据源配置,动态获取idp上的元数据-->
13 <util:map id="metadataResources">
14     <entry key="${完整idp域名https访问地址包括项目名}/entities/">
15         <bean class="org.opensaml.saml.metadata.resolver.filter.impl.MetadataFilterChain">
16             <property name="filters">
17                 <list />
18             </property>
19         </bean>
20     </entry>
21 </util:map>
22 <!--元数据过滤器配置-->
23 <bean id="metadataFilters"
24               class="org.opensaml.saml.metadata.resolver.filter.impl.MetadataFilterChain">
25     <property name="filters">
26         <list>
27             <!--
28                     <bean class="org.opensaml.saml.metadata.resolver.filter.impl.RequiredValidUntilFilter"
29                           c:maxValidity="0"  />
30                     -->
31             <bean class="org.opensaml.saml.metadata.resolver.filter.impl.SignatureValidationFilter"
32                           c:engine-ref="trustEngine" p:requireSignature="false"  />
33         </list>
34     </property>
35 </bean>
36 <bean id="trustEngine"
37               class="org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine"
38               c:keyInfoResolver-ref="keyInfoResolver"
39               c:resolver-ref="credentialResolver" />
40 <bean id="keyInfoResolver"
41               class="org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver">
42     <constructor-arg name="keyInfoProviders">
43         <list>
44             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider" />
45             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider" />
46             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.DEREncodedKeyValueProvider" />
47             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider" />
48         </list>
49     </constructor-arg>
50 </bean>
51 <bean id="credentialResolver"
52               class="org.opensaml.security.credential.impl.StaticCredentialResolver"
53               c:credential-ref="credentialFactoryBean" />
54 <bean id="credentialFactoryBean"
55               class="net.shibboleth.idp.profile.spring.relyingparty.security.credential.BasicResourceCredentialFactoryBean"
56               p:publicKeyInfo="classpath:inc-md-pub.pem" ></bean>
View Code

 

3.5 修改cas-server-webapp的login-webflow.xml内容

在view-state id="viewLoginForm" 节点提交动作新增saml2.0校验,修改大致如下:

<view-state id="viewLoginForm" view="casLoginView" model="credential">
    <binder>
        <binding property="username" required="true"/>
        <binding property="password" required="true"/>
        <!--
            <binding property="rememberMe" />
            -->
    </binder>
    <on-entry>
        <set name="viewScope.commandName" value="'credential'"/>
        <!--进入该视图节点时,进行saml2.0元数据校验-->
        <evaluate expression="samlMetadataUIParserAction" />
    </on-entry>
    <transition on="submit" bind="true" validate="true" to="realSubmit"/>
</view-state>

 

 

4.使用testshib作为sp端免费测试

参考教程:https://www.testshib.org/register.html

4.1 将我们的cas-server和idp.war都部署到开启了ssl的tomcat上进行启动吧!然后开启我们的花生壳内网映射!

clipboard(10)

4.2 开始上传idp端元数据文件

 clipboard(12)

4.3 修改idp端的元数据提供者配置

在idp安装目录下找到D:\Program Files (x86)\shibboleth-idp-server\conf\relying-party.xml

在节点<metadata:MetadataProvider id="ShibbolethMetadata" xsi:type="metadata:ChainingMetadataProvider">内插入testshib的idp元数据提供,插入后是这样子:

<metadata:MetadataProvider id="ShibbolethMetadata" xsi:type="metadata:ChainingMetadataProvider">
    <!-- Load the IdP's own metadata.  This is necessary for artifact support. -->
    <metadata:MetadataProvider id="IdPMD" xsi:type="metadata:FilesystemMetadataProvider"
                                   metadataFile="D:\Program Files (x86)\shibboleth-idp-server/metadata/idp-metadata.xml"
                                   maxRefreshDelay="P1D" />
    <!--test shib-->
    <metadata:MetadataProvider id="HTTPMetadataTESTSHIB"
                  xsi:type="metadata:FileBackedHTTPMetadataProvider"
                  backingFile="D:\Program Files (x86)\shibboleth-idp-server/metadata/testshib-providers.xml"
                  metadataURL="http://www.testshib.org/metadata/testshib-providers.xml"/>

4.4 开始测试

访问测试sp地址:https://sp.testshib.org/,在修改下方的idp访问地址,go!

 

111

clipboard(13)

clipboard(2)

clipboard(9)

clipboard(3)

 

 

账号自动登录后跳转子系统(webUtils修改的有问题,暂时不要参照此功能)

注意:修改全在cas服务器端修改

修改涉及文件

1.UsernamePasswordCredential

添加用于区别自动登录和正常登录的账号属性

内容如下:

package org.jasig.cas.authentication;

import org.apache.commons.lang3.builder.HashCodeBuilder;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

/**
 * Credential for authenticating with a username and password.
 *
 * @author Scott Battaglia
 * @author Marvin S. Addison
 * @since 3.0.0
 */
public class UsernamePasswordCredential implements Credential, Serializable {

    /** Authentication attribute name for password. **/
    public static final String AUTHENTICATION_ATTRIBUTE_PASSWORD = "credential";

    private static final long serialVersionUID = -700605081472810939L;


    @NotNull
    @Size(min=1, message = "required.username")
    private String username;
    /**
     * 用于区别自动登录用户名
     */
    private String autoAccount;

    @NotNull
    @Size(min=1, message = "required.password")
    private String password;

    /** Default constructor. */
    public UsernamePasswordCredential() {}

    /**
     * Creates a new instance with the given username and password.
     *
     * @param userName Non-null user name.
     * @param password Non-null password.
     */
    public UsernamePasswordCredential(final String userName, final String password) {
        this.username = userName;
        this.password = password;
    }

    public final String getPassword() {
        return this.password;
    }


    public final void setPassword(final String password) {
        this.password = password;
    }


    public final String getUsername() {
        return this.username;
    }

    public final void setUsername(final String userName) {
        this.username = userName;
    }

    public final String getAutoAccount() {
        return autoAccount;
    }

    public final void setAutoAccount(String autoAccount) {
        this.autoAccount = autoAccount;
    }


    @Override
    public String getId() {
        return this.username;
    }

    @Override
    public String toString() {
        return this.username;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        final UsernamePasswordCredential that = (UsernamePasswordCredential) o;

        if (password != null ? !password.equals(that.password) : that.password != null) {
            return false;
        }

        if (username != null ? !username.equals(that.username) : that.username != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(username)
                .append(password)
                .toHashCode();
    }

}

 

2.WebUtils的getTicketGrantingTicketId方法

添加获取在自动登录后的cookie参数,避免获取不到导致无法识别自动登录

/**
     * Gets the ticket granting ticket id from the request and flow scopes.
     *
     * @param context the context
     * @return the ticket granting ticket id
     */
    public static String getTicketGrantingTicketId(
            @NotNull final RequestContext context) {
        final String tgtFromRequest = (String) context.getRequestScope().get("ticketGrantingTicketId");
        final String tgtFromFlow = (String) context.getFlowScope().get("ticketGrantingTicketId");
        String tgtFromCookie=null;
        Cookie[] cookies=getHttpServletRequest(context).getCookies();g
        if(cookies!=null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("TGC")){
                    tgtFromCookie= cookie.getValue();
                }
            }
        }
        if(tgtFromRequest!=null){
            return tgtFromRequest;
        }else if(tgtFromFlow!=null){
            return tgtFromFlow;
        }else if(tgtFromCookie!=null){
            return tgtFromCookie;
        }else{
            return null;
        }
//        return tgtFromRequest != null ? tgtFromRequest : tgtFromFlow;

    }

 

 

3.QueryDatabaseAuthenticationHandler(jdbc验证类)的authenticateUsernamePasswordInternal方法

添加根据自动登录参数验证的操作

@Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {

        if (StringUtils.isBlank(this.sql) || getJdbcTemplate() == null ) {
            throw new GeneralSecurityException("Authentication handler is not configured correctly");
        }

        if(credential.getAutoAccount()!=null){
            final String username = credential.getAutoAccount();
            try {
                final String encryptedPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
            } catch (final IncorrectResultSizeDataAccessException e) {
                if (e.getActualSize() == 0) {
                    throw new AccountNotFoundException(username + " not found with SQL query");
                } else {
                    throw new FailedLoginException("Multiple records found for " + username);
                }
            } catch (final DataAccessException e) {
                throw new PreventedException("SQL exception while executing query for " + username, e);
            }
            return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
        }else {
            final String username = credential.getUsername();
            final String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword());
            try {
                final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
                if (!dbPassword.equals(encryptedPassword)) {
                    throw new FailedLoginException("Password does not match value on record.");
                }
            } catch (final IncorrectResultSizeDataAccessException e) {
                if (e.getActualSize() == 0) {
                    throw new AccountNotFoundException(username + " not found with SQL query");
                } else {
                    throw new FailedLoginException("Multiple records found for " + username);
                }
            } catch (final DataAccessException e) {
                throw new PreventedException("SQL exception while executing query for " + username, e);
            }
            return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
        }
    }

 

4.TicketGrantingTicketCheckAction(tgt验证类)的doExecute方法

添加根据自动登录后把cookie放回 login流程的requestScope操作,否则默认流程login会提示没有登录

@Override
    protected Event doExecute(final RequestContext requestContext) throws Exception {
        final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext);
        if (!StringUtils.hasText(tgtId)) {
            return new Event(this, NOT_EXISTS);
        }

        String eventId = INVALID;
        try {
            final Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class);
            if (ticket != null && !ticket.isExpired()) {
                eventId = VALID;
                //有效则放入
                WebUtils.putTicketGrantingTicketInScopes(requestContext, tgtId);
            }
        } catch (final AbstractTicketException e) {
            logger.trace("Could not retrieve ticket id {} from registry.", e);
        }
        return new Event(this,  eventId);
    }

 

5.AutoLoginController自动登录入口

package com.sbs.cas.controller;/**
 * @description
 * @autor xbwu on 2017/9/9.
 */

import org.apache.commons.lang3.StringUtils;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.*;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.web.support.WebUtils;
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.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.CookieGenerator;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;

/**
 * 自动登录接口
 * @author xbwu
 * @create 2017-09-09 
 **/
@Controller(value = "/autoLogin")
public class AutoLoginController {

    protected final transient Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    @Qualifier(value = "centralAuthenticationService")
    private CentralAuthenticationService centralAuthenticationService;

    @Autowired
    @Qualifier(value = "ticketGrantingTicketCookieGenerator")
    private CookieGenerator ticketGrantingTicketCookieGenerator;
    @NotNull
    @Autowired(required=false)
    @Qualifier("defaultAuthenticationSystemSupport")
    private AuthenticationSystemSupport authenticationSystemSupport;

    @RequestMapping(value = "/checkLogin")
    public ModelAndView autoLogin(HttpServletRequest request,HttpServletResponse response,Model model) throws Exception{
        String systemUrl=request.getParameter("systemUrl");
        String autoAccount=request.getParameter("autoAccount");
        if(StringUtils.isBlank(autoAccount)){
            throw new Exception("参数错误");
        }
        bindTicketGrantingTicket(autoAccount,request,response);
        response.sendRedirect(systemUrl);
        return null;
    }


    protected void bindTicketGrantingTicket(String autoAccount,HttpServletRequest request, HttpServletResponse response){
        try {
            UsernamePasswordCredential credential = new UsernamePasswordCredential();
            credential.setAutoAccount(autoAccount);
            credential.setUsername(autoAccount);
            final AuthenticationContextBuilder builder = new DefaultAuthenticationContextBuilder(
                    this.authenticationSystemSupport.getPrincipalElectionStrategy());
            final AuthenticationTransaction transaction =
                    AuthenticationTransaction.wrap(credential);
            this.authenticationSystemSupport.getAuthenticationTransactionManager().handle(transaction,  builder);
            final AuthenticationContext authenticationContext = builder.build(null);
            final TicketGrantingTicket tgt = this.centralAuthenticationService.createTicketGrantingTicket(authenticationContext);
            ticketGrantingTicketCookieGenerator.addCookie(response, tgt.getId());
        } catch (Exception e){
            e.printStackTrace();
            logger.error("bindTicketGrantingTicket has exception.", e);
        }
    }
}

 

6.web.xml配置自动登录路径通过springmvc请求

<servlet-mapping>
        <servlet-name>cas</servlet-name>
        <url-pattern>/autoLogin/*</url-pattern>
    </servlet-mapping>

 

完工!我的本地测试路径:http://localhost:8080/cas/autoLogin/checkLogin?systemUrl=http://casTest01.com:8081&autoAccount=admin

 

demo下载:https://code.aliyun.com/cas/cas-overlay

posted @ 2017-09-11 11:48  痴心贼  阅读(3741)  评论(5编辑  收藏  举报