文章目录
  1. 环境配置#
  • 目录#
  • #
    1. #
      1. #
      2. #
    2. #
      1. #
      2. #
        1. 1.SSL配置#
        2. 2.cas部署#
  • #
  • #
  • CAS单点登陆(SSO)开发指南

    本教程使用的SSO服务器是Yelu大学研发的CAS(Central Authentication Server),
    官网:http://www.jasig.org/cas
    下载地址:http://downloads.jasig.org/
    文档地址:http://jasig.github.io/cas/4.0.x/index.html
    本文修改的cas-server项目代码我已经放到了github上 https://github.com/pkaq/cas-server-3.5.2

    环境配置#

    JDK:1.7.45
    CAS版本:4.0.0

    目录#

    1. 环境准备
      _A. 证书生成
      __
      1. 导出证书
      __2. 导入服务端信任证书
      __3. 颁发给客户机
      _B. WEB服务器配置
      __
      1. tomcat配置
      __2. jboss配置
      __3. webloigc配置
      __4. websphere配置
    2. 服务端搭建
    3. 客户端搭建
      ___a. JAVA客户端应用搭建
    4. 进阶应用
      _a. 配置自定义校验规则
      _
      b. 配置自定义认证返回参数
      ___c. 配置自定义界面

    #

    #

    用JDK自带的keytool工具生成证书:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    	keytool -genkey -alias wsria -keyalg RSA -keystore /home/mykey
    ```
    参数说明如图:
    ![生成秘钥](/images/2015/01/use-keytool-create-key.gif)

    p.s:
    1.也可以申请免费的startssl证书,具体申请过程请移步
    https://www.startssl.com/?app=11&action=regform
    当然国内也有一家wosign可以申请,申请速度比startssl要快一些,而且在线客服态度很好可以提供很好的帮助
    www.wosign.com

    2.具体的输入项图片中都有说明,有一点我要解释一下;在输入完密码后提示输入域名是我输入的是sso.wsria.com,其实这个域名是不存在的,但是我为了演示所以虚拟了这个域名,技巧在于修改

    C:\Windows\System32\drivers\etc\hosts
    添加内容如下:

    127.0.0.1 sso.wsria.com
    这样在访问sso.wsria.com的时候其实是访问的127.0.0.1也就是本机

    #### <a name="export">1.导出证书</a>
    ```shell
    keytool -export -file /home/my.crt -alias wsria -keystore /home/mykey

    #

    1
    keytool -import -trustcacerts -alias [keyEntry_name] -file mycert.crt -keystore [keystore_name]

    #

    1
    keytool -import -file /opt/name.cer -keystore $JAVA_HOME/jre7/lib/security/cacerts -alias server

    这里默认密码是changeit

    #

    #

    首先要启用Web服务器(Tomcat)的SSL,也就是HTTPS加密协议;
    打开tomcat目录的conf/server.xml文件,找到如下行并设置keystoreFile、keystorePass修改结果如下:

    1
    2
    3
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true" maxThreads="150"
    scheme="https" secure="true" clientAuth="false" sslProtocol="TLS"
    keystoreFile="/home/mykey" keystorePass="xxxx" />

    参数说明:
    keystoreFile:在第一步创建的key存放位置
    keystorePass:创建证书时的密码

    p.s
    按同样的方法来配置Tomcat 7却启动不起来,报如下错误:

    严重: Failed to initialize end point associated with ProtocolHandler [“http-apr-8443”]
    java.lang.Exception: Connector attribute SSLCertificateFile must be defined when using SSL with APR

    仔细看上面的异常信息发现这是APR报的错误。Tomcat 6也有APR包但我从来都没用过。为此查看了Tomcat的ssl-how,在“Edit the Tomcat Configuration File”一节中说到:
    Tomcat提供了两个SSL实现,一个是JSSE实现,另一个是APR实现。
    Tomcat将自动选择使用哪个实现,即如果安装了APR则自动选择APR,否则选择JSSE。
    如果不希望让Tomcat自动选择,而是我们自己指定一个实现则可通过protocol定义,如下:

    我又查看了6.0的相同说明,里面与7.0的说明一模一样。因此问题只可能是:是否安装了APR包。

    以前只听说过APR但没弄过。APR是什么文件?后来才发现APR文件名为tcnative-1.dll。进一步检查6.0和7.0的安装目录,结果发现6.0里没这个dll文件,而7.0里有。换句话说,6.0默认使用JSSE实现,而7.0默认使用APR实现。

    弄明白缘由就好办了。由于习惯使用6.0的配置方式(即JSEE实现),因此只要把上面conf\server.xml里的protocol修改一下就行了:

    <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS"
               keystoreFile="${user.home}/.keystore"
               keystorePass="changeit" />
    

    重新启动,一切正常。

    #
    1.SSL配置#

    找到standalone->configuration->standalone.xml
    修改节点为如下内容

    1
    2
    3
    4
    5
    <connector name="https" protocol="HTTP/1.1" scheme="https" socket-binding="https" secure="true" enabled="true">
    <ssl name="https" key-alias="1" password="Masterfrank099"
    certificate-key-file="D:/key/domainname.jks"
    protocol="all" verify-client="false"/>
    </connector>

    key-alias:证书别名
    password:证书密码
    certificate-key-file:证书放位置

    p.s:JBOSS 4.2以上版本服务启动如果不加任何参数的话,只监听127.0.0.1,就是说只能用127.0.0.1或者localhost访问,用本机的对外地址是访问不了,同一网络内别的机子无法访问。

    解决方案:打开jboss7/standalone/configuration/standalone.xml,找到如下代码

    1
    2
    3
    <interface name="public">
    <inet-address value="${jboss.bind.address:127.0.0.1}"/>
    </interface>

    将127.0.0.1改为0.0.0.0即可解决jboss无法使用IP地址访问的问题。

    2.cas部署#

    1、将war包放到jboss7/standalone/ deployments目录下,启动jboss,如可正常访问,说明部署成功。
    2、 若直接将car.war部署到jboss中,在启动时就会报错,引起错误的原因:
    1) 由于jboss自带有log4j日志,这就会引起jboss的log4j与cas的log4j发生冲突,将cas中引入log4j.xml的代码删除即可。
    2) 未指定hibernate的方言,打开cas.war\WEB-INF\classes\META-INF\ persistence.xml,将如下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    <properties>
    <property name="hibernate.dialect"value="org.hibernate.dialect.HSQLDialect"/>
    </properties>
    //放到
    <persistence-unit name="CasPersistence" transaction-type="RESOURCE_LOCAL">下。
    ```

    ##### <a name="weblogic">c.WebLogic</a>
    Weblogic默认端口为7001
    请移步:[\[weblogic 12c安装\] - ssl配置/cas部署](\2015\01\26\installweblogic\index.html)
    p.s:
    1.官方提供的zip压缩版经测试无法正常访问ssl,可能少东西
    2.打成war包部署会报log4j.xml找不到错误,直接以目录形式部署没问题,出现该问题的原因应该是加载冲突所致,可考虑用serverlet方式加载spring的log4j listener

    #####<a name="websphere">d.WebSphere</a>
    Weblogic默认端口为9080
    请移步:[\[websphere 8.5.5安装\] - ssl配置/cas部署](\2015\01\27\websphereinstall\index.html)

    ## <a name="server">二、CAS服务端搭建</a>
    1.CAS服务端下载:http://www.jasig.org/cas/download
    2.解压cas-server-xxx/modules/cas-server-webapp-xxx.war,改名为cas,然后复制cas目录到你的tomcat/webapp目录下
    3.访问http://localhost:8080/cas即可看到项目首页,输入casuser/Mellon点击登录(这是4.0.x之后版本的用户名密码,3.5.x版本CAS默认的验证规则只要用户名和密码相同就通过)
    所以如果你看到下面的这张图片你就成功了
    ![登陆成功](/images/2015/01/cas-login-success.gif)

    ## <a name="client">三、CAS客户端</a>
    ### <a name="withjava">A.Java客户端集成</a>
    1.在客户端添加 "org.jasig.cas.client:cas-client-core:+",依赖
    2.配置客户端web.xml文件

    ```xml
    <!-- 单点登陆配置开始 -->
    <!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置-->
    <listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>
    <!-- 该过滤器用于实现单点登出功能,可选配置。 -->
    <filter>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 该过滤器负责用户的认证工作,必须启用它 -->
    <filter>
    <filter-name>CASFilter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <!-- 服务器登陆页面url
    casServerUrlPrefix 参数的值是cas 服务的访问地址。cas client 验证ticket 的时候,
    要访问cas 服务的/serviceValidate 接口,使用的url 就是${ casServerUrlPrefix }serviceValidate ,
    因为客户web 应用要验证cas 的证书,所以证书cn 字段的值必须和casServerUrlPrefix 里设置的cas 服务器的域名保持一致,
    并且在web 应用的服务器上配置cas 服务的访问域名。
    -->
    <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>https://www.sifenggu.com:8443/cas/login</param-value>
    </init-param>
    <!--这里的server是服务端的IP-->
    <init-param>
    <param-name>serverName</param-name>
    <param-value>http://localhost:9090</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>CASFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 该过滤器负责对Ticket的校验工作,必须启用它 ,如果要从服务器获取用户名之外的更多信息应该采用cas20这个2.0协议的代理-->
    <filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <!-- casServerUrlPrefix 参数的值是cas 服务的访问地址。
    cas client 验证ticket 的时候,要访问cas 服务的/serviceValidate 接口,
    使用的url 就是${ casServerUrlPrefix }serviceValidate ,因为客户web 应用要验证cas 的证书,
    所以证书cn 字段的值必须和casServerUrlPrefix 里设置的cas 服务器的域名保持一致,并且在web 应用的服务器上配置cas 服务的访问域名。 -->
    <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>https://www.sifenggu.com:8443/cas</param-value>
    </init-param>
    <!--serverName 参数,cas client 会用来生成service 参数,并且cas 服务器在认证通过、ticket 验证通过后,
    会redirect 到web 应用,redirect 的url 就是service 参数的值。serverName 参数可以是IP ,也可以是域名,只要保证浏览器能访问到即可。 -->
    <init-param>
    <param-name>serverName</param-name>
    <param-value>http://localhost:9090</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--
    该过滤器负责实现HttpServletRequest请求的包裹,
    比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
    -->
    <filter>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--
    该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
    比如AssertionHolder.getAssertion().getPrincipal().getName()。
    -->
    <filter>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 自动根据单点登录的结果设置本系统的用户信息 -->
    <filter>
    <display-name>AutoSetUserAdapterFilter</display-name>
    <filter-name>AutoSetUserAdapterFilter</filter-name>
    <filter-class>demo.frank.wu.sso.cas.AutoSetUserAdapterFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>AutoSetUserAdapterFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 单点登陆配置结束 -->
    ```


    ## <a name="adv">四、进阶应用</a>
    ### <a name="adv_1">A.配置自定义校验规则</a>

    打开cas-server-webapp项目,找到src/main/webapp/WEB-INF/deployerConfigContext.xml,将primaryAuthenticationHandler这个bean注释掉,用如下bean替代
    ```xml
    <!-- 自定义数据库鉴权 -->
    <bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
    <property name = "dataSource" ref="dataSource"/>
    <property name = "sql" value="select upper(password) from itsm_user where loginname= ?"/>
    <property name = "passwordEncoder" ref="MD5PasswordEncoder"/>
    </bean>
    ```
    在其它位置配置数据源以及自定义加密的bean
    ```xml
    <!-- datasource数据源,采用此数据源需要将druid和ojdbc依赖添加到项目中 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="username" value="xxx"/>
    <property name="password" value="xxx"/>
    <property name="url" value="jdbc:oracle:thin:@xxx.xxx.xxx.xx:1521:xxx"/>
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="10" />
    <property name="minIdle" value="10" />
    <property name="maxActive" value="120" />
    </bean>
    <!-- MD5加密 -->
    <bean id="MD5PasswordEncoder" class="org.jasig.cas.util.MD5Encoder"/>

    关于自定义密码加密的MD5Encoder类,只要自己写一个类实现org.jasig.cas.authentication.handler.PasswordEncoder接口中的
    public String encode(String password)
    方法即可,该方法的返回值是加密处理后的密码。 当然如果你用MD5加密可以采用内置的MD5PasswordEncoder;

    修改完成后重新打包cas-server-webapp项目
    P.S 1:连接数据库需要增加对cas-server-support-jdbc项目和数据源以及数据库驱动的依赖,关于如何向pom添加依赖,请自补;当然最好给根POM配置一下国内的maven库(例如开源中国的),否则不仅会很慢,很多依赖也无法下载。

    2:由于Oracle的原因 maven库中并未提供oracle驱动,而maven又不像gradle那样可以简易友好的加载本地依赖,所以需要你找到oracle驱动并且 注 册 到 本 地 仓 库
    mvn install:install-file -Dfile={Path/to/your/ojdbc.jar} -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0 -Dpackaging=jar

    部署到tomcat之后便可以通过数据库中的正式用户进行登陆了.如果页面提示”您提供的凭证有误”多半为数据源配置或密码不匹配所致。建议检查数据源是否配置正确以及encoder加密返回的密码与数据库存储的加密密码(区分大小写)是否一致。

    修改完成后采用mavenpackge命令或者直接再eclipse中执行maven install进行构建即可.

    #

    默认情况下cas认证完成后只会返回username给客户端,如果需要返回更多信息需要修改deployerConfigContext.xml文件,同时配置attributeRepository如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <bean id="attributeRepository"
    class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">
    <!--指定数据源-->
    <constructor-arg index="0" ref="dataSource" />
    <!--指定查询sql-->
    <constructor-arg index="1" value="select username,loginname from ITSM_USER where {0}" />

    <!-- 组装sql用的查询条件属性 -->
    <property name="queryAttributeMapping">
    <map>
    <!-- key必须是uername而且是小写否则会导致取不到用户的其它信息,value对应数据库用户名字段,系统会自己匹配 -->
    <entry key="username" value="loginname" />
    </map>
    </property>
    <property name="resultAttributeMapping">
    <map>
    <!-- key为对应的数据库字段名称,value为提供给客户端获取的属性名字,系统会自动填充值 -->
    <entry key="loginname" value="loginname"/>
    <entry key="username" value="username"/>
    </map>
    </property>
    </bean>

    <bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">
    <property name="registeredServices">
    <list>
    <bean class="org.jasig.cas.services.RegexRegisteredService">
    <property name="id" value="0" />
    <property name="name" value="HTTP and IMAP" />
    <property name="description" value="Allows HTTP(S) and IMAP(S) protocols" />
    <property name="serviceId" value="^(https?|imaps?)://.*" />
    <property name="evaluationOrder" value="10000001" />
    <!-- 此属性相当关键,必须配置后客户端才能获取信息 -->
    <property name="allowedAttributes">
    <list>
    <value>username</value>
    <value>loginname</value>
    </list>
    </property>
    </bean>
    </list>
    </property>
    </bean>

    找到casServiceValidationSuccess.jsp。此文件作用是在server验证成功后,这个页面负责生成与客户端交互的xml信息,在默认的casServiceValidationSuccess.jsp中,只包括用户登录名,并不提供其他的属性信息,因此需要对页面进行扩展。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <%@ page session="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
    <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationSuccess>
    <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
    <c:if test="${not empty pgtIou}">
    <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
    </c:if>
    <c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
    <cas:proxies>
    <c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
    <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
    </c:forEach>
    </cas:proxies>
    </c:if>
    <!-- 在server验证成功后,这个页面负责生成与客户端交互的xml信息,在默认的casServiceValidationSuccess.jsp中,只包括用户名,并不提供其他的属性信息,因此需要对页面进行扩展 -->
    <c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) > 0}">
    <cas:attributes>
    <c:forEach var="attr"
    items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}">
    <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>
    </c:forEach>
    </cas:attributes>
    </c:if>
    </cas:authenticationSuccess>
    </cas:serviceResponse>

    客户端获取用户信息

    1
    2
    3
    4
    5
    AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
    String loginName = principal.getName();
    System.out.println("loginName:" + loginName);
    Map<String, Object> attributes = principal.getAttributes();
    System.out.println("username:" + attributes.get("username"));

    #

    1.cas统一认证的登陆页面位于:cas目录/WEB-INF/view/jsp/default 文件夹里,其中ui/casLoginView.jsp为登陆页面

    2.首先复制一份default文件夹 重命名为myview

    3.然后复制classes/default.properties 到 classes/mytheme.properties 打开mytheme.properties 修改登陆页面的路径为我们复制的myview 文件夹。

    1
    2
    3
    ## 修改登陆页面为自定义页面
    ## mod by wuf 2015/1/16
    casLoginView.url=/WEB-INF/view/jsp/myview/ui/casLoginView.jsp

    4 修改 cas目录/cas.properties 中 cas.viewResolver.basename =mytheme

    到这一步我们只是将登陆页面拷贝了一份然后指向这份拷贝,接下来我们就可以随意修改我们拷贝的页面,这样做的目的是如果以后想还原回来比较方便,只需要修改引用就行。

    接下来我们修改casLoginView.jsp页面:

    修改的要求是:需要保留登陆form表单(但可以修改样式,虽然表单标签是带前缀的,但和普通html标签一样修改其样式,也可以将所有的信息输出删除 如)但必须保留保单中的用户名、密码输入框,确认按钮,而且最好复制其标签,修改其class属性来修改样式.

    __参考资料
    Jboss as 7 wiki - Installing cas-web WebLogic上CAS服务器搭建
    WebSphere上搭建CAS Server CAS实现单点登陆的原理
    CAS单点登陆(SSO)完整教程 修改cas登陆页面-服务器端
    *单点登录 - CAS【四】获取更全面的用户信息

    支持一下
    您得支持,是我前进的动力.