zoukankan      html  css  js  c++  java
  • 【原创】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-webappsrcmain esourcesservicesHTTPSandIMAPS-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 FilesJavajdk1.8.0_131jrelibsecurity

    执行删除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-webappuild.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-webappsrcmain esourceslog4j2.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-webappsrcmainwebappWEB-INF下的view文件夹一份,命名为:view_custom

    clipboard

    1.7.2 修改cas-server-webappsrcmainwebappWEB-INFcas.properties内容:

    clipboard(1)

    1.7.3 样式文件-修改cas-server-webappsrcmain esourcescas-theme-default.properties的内容:

    clipboard(11)

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

    clipboard(6)

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

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

    cas-server-webappsrcmainwebappWEB-INFcas.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.5lib,这样安装好的idp就会有这个jar包

    2.2 执行install.bat安装idp

    clipboard(4)

    2.3 修改idp的元数据文件

    D:Program Files (x86)shibboleth-idp-servermetadataidp-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-serverconf elying-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

  • 相关阅读:
    【树转数组】poj1195
    sigsuspend sigprocmask函数的用法
    [javascirpt] Regex
    [Angular 2] Passing data to components with 'properties'
    [TypeScript] Inheritance
    [Angular 2] Pipes with Multiple Parameters
    [Angular 2] Create a simple search Pipe
    [Angular 2] Pipe Purity
    [Angular 2] Exposing component properties to the template
    [Angular 2] ng-model and ng-for with Select and Option elements
  • 原文地址:https://www.cnblogs.com/chixinzei/p/7504272.html
Copyright © 2011-2022 走看看