天天看點

CAS學習記錄2--CAS Server的改造

一、前言

之前整理完CAS Server的安裝,現在開始做CAS Server的改造

二、語言改造

CAS預設是使用英文的,雖然在界面上可以切換語言,但還是預設成中文的比較好

打開到WEB-INF/cas-servlet.xml,找到bean id="localeResolver",将預設語言改為zh_CN

CAS學習記錄2--CAS Server的改造

重新開機Tomcat,清除浏覽器緩存就可以了

CAS學習記錄2--CAS Server的改造

三、賬号認證方式改造

CAS支援多種認證方式,現在賬号認證基本都是用資料庫查詢,增加這一部分

首先修改pom.xml,增加資料庫通路依賴項

<dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.0.8</version>
    </dependency>
    <dependency>
      <groupId>org.jasig.cas</groupId>
      <artifactId>cas-server-support-jdbc</artifactId>
      <version>${project.version}</version>
    </dependency>
           

打開WEB-INF/deployerConfigContext.xml,注釋掉bean id="primaryAuthenticationHandler",并增加資料庫通路的相關bean,連接配接池用c3p0,内置支援,也試過dbcp,但是有點沖突,應該可以解決,不想花這時間了

<!--
       | TODO: Replace this component with one suitable for your enviroment.
       |
       | This component provides authentication for the kind of credential used in your environment. In most cases
       | credential is a username/password pair that lives in a system of record like an LDAP directory.
       | The most common authentication handler beans:
       |
       | * org.jasig.cas.authentication.LdapAuthenticationHandler
       | * org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler
       | * org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler
       | * org.jasig.cas.support.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler
       -->
    <!--<bean id="primaryAuthenticationHandler"-->
          <!--class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">-->
        <!--<property name="users">-->
            <!--<map>-->
                <!--<entry key="casuser" value="Mellon"/>-->
            <!--</map>-->
        <!--</property>-->
    <!--</bean>-->
    <bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
        <property name="dataSource" ref="casDataSource" />
        <property name="sql" value="select password from user where lower(login_name) = lower(?)" />
        <property name="passwordEncoder"  ref="passwordEncoder"/>
    </bean>
    <bean id="passwordEncoder" class="org.jasig.cas.custome.Md5PasswordEncoder"/>

    <!-- c3p0連接配接池配置 -->
    <bean id="casDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 使用者名-->
        <property name="user" value="root"/>
        <!-- 使用者密碼-->
        <property name="password" value="123456"/>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"/>
        <!--連接配接池中保留的最大連接配接數。預設值: 15 -->
        <property name="maxPoolSize" value="50"/>
        <!-- 連接配接池中保留的最小連接配接數,預設為:3-->
        <property name="minPoolSize" value="5"/>
        <!-- 初始化連接配接池中的連接配接數,取值應在minPoolSize與maxPoolSize之間,預設為3-->
        <property name="initialPoolSize" value="5"/>
        <!--最大空閑時間,60秒内未使用則連接配接被丢棄。若為0則永不丢棄。預設值: 0 -->
        <property name="maxIdleTime" value="60"/>
        <!-- 當連接配接池連接配接耗盡時,用戶端調用getConnection()後等待擷取新連接配接的時間,逾時後将抛出SQLException,如設為0則無限期等待。機關毫秒。預設: 0 -->
        <property name="checkoutTimeout" value="3000"/>
        <!--當連接配接池中的連接配接耗盡的時候c3p0一次同時擷取的連接配接數。預設值: 3 -->
        <property name="acquireIncrement" value="3"/>
        <!--定義在從資料庫擷取新連接配接失敗後重複嘗試的次數。預設值: 30 ;小于等于0表示無限次-->
        <property name="acquireRetryAttempts" value="1"/>
        <!--重新嘗試的時間間隔,預設為:1000毫秒-->
        <property name="acquireRetryDelay" value="1000" />
        <!--關閉連接配接時,是否送出未送出的事務,預設為false,即關閉連接配接,復原未送出的事務 -->
        <property name="autoCommitOnClose" value="false" />
        <!--如果為false,則擷取連接配接失敗将會引起所有等待連接配接池來擷取連接配接的線程抛出異常,但是資料源仍有效保留,并在下次調用getConnection()的時候繼續嘗試擷取連接配接。如果設為true,那麼在嘗試擷取連接配接失敗後該資料源将申明已斷開并永久關閉。預設: false-->
        <property name="breakAfterAcquireFailure" value="false"/>
        <!--每60秒檢查所有連接配接池中的空閑連接配接。預設值: 0,不檢查 -->
        <property name="idleConnectionTestPeriod" value="60"/>
        <!--c3p0全局的PreparedStatements緩存的大小。如果maxStatements與maxStatementsPerConnection均為0,則緩存不生效,隻要有一個不為0,則語句的緩存就能生效。如果預設值: 0-->
        <property name="maxStatements" value="0"/>
        <!--maxStatementsPerConnection定義了連接配接池内單個連接配接所擁有的最大緩存statements數。預設值: 0 -->
        <property name="maxStatementsPerConnection" value="0"/>
    </bean>
           

這裡需增加一個Md5PasswordEncoder的類,如果密碼用其他加密方式,也可以參照修改,特别說明的是Maven會檢查檔案的版權聲明,和代碼樣式,建議複制一個原有類來改,免得又有七七八八的錯

在cas-server-webapp下建立包org.jasig.cas.custom,包下建立類Md5PasswordEncoder,代碼如下:

/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jasig.cas.custom;

import org.apache.commons.codec.digest.DigestUtils;
import org.jasig.cas.authentication.handler.PasswordEncoder;
import java.io.UnsupportedEncodingException;

/**
 * Default password encoder for the case where no password encoder is needed.
 * Encoding results in the same password that was passed in.
 *
 * @author tsfdez

 * @since 3.0.0
 */
public final class Md5PasswordEncoder implements PasswordEncoder {
    // 轉MD5的簽名密鑰
    private static final String MD5_KEY="67hdskj73nhsdf7";
    private static final String INPUT_CHARSET = "utf-8";

    @Override
    public String encode(final String password) {
        try {
            return DigestUtils.md5Hex((MD5_KEY + password + MD5_KEY).getBytes(INPUT_CHARSET));
        } catch (final UnsupportedEncodingException e) {
            throw new RuntimeException("MD5簽名過程中出現錯誤,指定的編碼集不對,您目前指定的編碼集是:" + INPUT_CHARSET, e);
        }
    }
}
           

重新啟動Tomcat,試下用資料庫裡的賬号登入,登入成功

CAS學習記錄2--CAS Server的改造

四、主題改造

先說個問題,打開CAS頁面的時候,浏覽器會一直轉圈載入,雖說功能也能用

CAS學習記錄2--CAS Server的改造

十有八九是直接引用到國外的CSS或者JS了,翻翻頁面代碼,果然如此,因為網絡的關系,國外的軟體多少都有這個問題

打開webapp/js/cas.js,這裡用了背景加載,看到帶google字樣估計就有問題了

var scripts = [ "https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js",
    "https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js",
    "https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js",
    "https://cdn.rawgit.com/cowboy/javascript-debug/master/ba-debug.min.js"];

head.ready(document, function() {
    head.load(scripts, resourceLoadedSuccessfully);
});
           

沒關系,CAS是支援主題的,換個試試,除了預設主題,還自帶一個apereo主題

CAS學習記錄2--CAS Server的改造

打開WEB-INF/cas.properties,做如下修改

#cas.themeResolver.defaultThemeName=cas-theme-default
cas.themeResolver.defaultThemeName=apereo
           

重新開機Tomcat,看看效果

CAS學習記錄2--CAS Server的改造

樣式變了,也不轉圈了,這個主題比起預設主題更加單純,可以在它基礎上修改出自定義的主題樣式

公司都有自己的登入頁面,可以在WEB-INF/view/jsp/default/ui/casLoginView.jsp裡修改,其他頁面不用改,一般登入完就跳回原系統了

CAS學習記錄2--CAS Server的改造

五、登出改造

登出使用https://localhost:9001/cas/logout這個位址

CAS學習記錄2--CAS Server的改造

實際使用中,要求登出後傳回到登入界面的,而CAS預設是不支援傳回跳轉的,這裡需要做個修改

打開WEB-INF/cas-servlet.xml,基本上業務邏輯都在這裡了,搜尋logout,找到這個

<bean id="logoutAction" class="org.jasig.cas.web.flow.LogoutAction"
        p:servicesManager-ref="servicesManager"
        p:followServiceRedirects="${cas.logout.followServiceRedirects:false}"/>
           

配置在屬性檔案裡,打開WEB-INF/cas.properties,預設是false的,改成true,這樣就可以加?service的方式跳轉了

# Specify whether CAS should redirect to the specified service parameter on /logout requests
cas.logout.followServiceRedirects=true
           

六、驗證碼改造

這裡用google的Kaptcha來實作,可以用servlet方式配置,也可以用spring方式,選用servlet的

首先打開cas-server-webapp的pom.xml檔案,增加Kaptcha依賴

<dependency>
      <groupId>com.github.penggle</groupId>
      <artifactId>kaptcha</artifactId>
      <version>2.3.2</version>
    </dependency>
           

打開WEB-INF/web.xml,增加servlet配置

<!-- 驗證碼功能 -->
    <servlet>
        <servlet-name>Kaptcha</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
        <init-param>
            <param-name>kaptcha.border</param-name>
            <param-value>no</param-value>
        </init-param>
        <init-param>
            <param-name>kaptcha.textproducer.char.space</param-name>
            <param-value>5</param-value>
        </init-param>
        <init-param>
            <param-name>kaptcha.textproducer.char.length</param-name>
            <param-value>5</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>Kaptcha</servlet-name>
        <url-pattern>/captcha.jpg</url-pattern>
    </servlet-mapping>
           

打開casLoginView.jsp,在password後增加驗證碼顯示

<section class="row">
            <label for="captcha"><spring:message code="screen.welcome.label.captcha" /></label>
            <spring:message code="screen.welcome.label.captcha.accesskey" var="captchaAccessKey" />
            <table>
                <tr>
                    <td>
                        <form:input cssClass="required" cssErrorClass="error" id="captcha" size="10" tabindex="2" path="captcha"  accesskey="${captchaAccessKey}" htmlEscape="true" autocomplete="off" />
                    </td>
                    <td style="vertical-align: bottom;">
                        <img οnclick="this.src='captcha.jpg?'+Math.random()" width="93" height="30" src="captcha.jpg">
                    </td>
                </tr>
            </table>
        </section>
           

找到語言檔messages_zh_CN.properties,在password後增加

screen.welcome.label.captcha=\u9A8C\u8BC1\u7801:
screen.welcome.label.captcha.accesskey=c
           

在org.jasig.cas.custom包下建立UsernamePasswordCredentialWithCaptcha.java,代碼如下:

/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jasig.cas.custom;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.jasig.cas.authentication.UsernamePasswordCredential;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * 帶驗證碼的登入界面.
 *
 * @author tsfdez
 * @since 3.0.0
 */
public class UsernamePasswordCredentialWithCaptcha extends UsernamePasswordCredential {

    /**
     * 驗證碼.
     */
    @NotNull
    @Size(min = 1, message = "captcha.required")
    private String captcha;

    public String getCaptcha() {
        return captcha;
    }

    public void setCaptcha(final String captcha) {
        this.captcha = captcha;
    }

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

        final UsernamePasswordCredentialWithAuthCode that = (UsernamePasswordCredentialWithAuthCode) o;

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

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

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(getUsername())
                .append(getPassword()).append(captcha).toHashCode();
    }
}
           

打開WEB-INF/webflow/login/login-webflow.xml,修改設定

<!--<var name="credential" class="org.jasig.cas.authentication.UsernamePasswordCredential"/>-->
    <var name="credential" class="org.jasig.cas.custom.UsernamePasswordCredentialWithCaptcha"/>
           

好了,重新開機一下Tomcat,看看效果,有顯示驗證碼,但是沒有效果,還需繼續修改

CAS學習記錄2--CAS Server的改造

在org.jasig.cas.custom包下建立AuthenticationViaFormActionWithCaptcha.java,代碼如下:

/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jasig.cas.custom;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.jasig.cas.authentication.Credential;
import org.jasig.cas.web.flow.AuthenticationViaFormAction;
import org.jasig.cas.web.support.WebUtils;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

/**
 * 驗證碼校驗類.
 *
 * @author tsfdez
 * @since 3.0.0
 */
public class AuthenticationViaFormActionWithCaptcha extends AuthenticationViaFormAction {

    /**
     * 驗證碼校驗.
     * @param context context
     * @param credentials credentials
     * @param messageContext messageContext
     * @return the resulting event.
     * @since 3.0.0
     */
    public final Event validatorCode(final RequestContext context, final Credential credentials,
                                      final MessageContext messageContext) {
        final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
        final HttpSession session = request.getSession();
        final String captcha = (String) session.getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
        session.removeAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);

        final UsernamePasswordCredentialWithCaptcha upc = (UsernamePasswordCredentialWithCaptcha) credentials;
        final String submitCaptcha = upc.getCaptcha();

        if (StringUtils.isEmpty(submitCaptcha) || StringUtils.isEmpty(captcha)) {
            messageContext.addMessage(new MessageBuilder().error().code("captcha.required").build());
            return new Event(this, ERROR);
        }
        if (submitCaptcha.equals(captcha)) {
            return new Event(this, SUCCESS);
        }
        messageContext.addMessage(new MessageBuilder().error().code("error.authentication.captcha.bad").build());
        return new Event(this, ERROR);
    }
}
           

找到語言檔messages_zh_CN.properties,在password.required後增加

error.invalid.loginticket=\u60a8\u4e0d\u80fd\u591f\u518d\u6b21\u63d0\u4ea4\u5df2\u7ecf\u63d0\u4ea4\u8fc7\u7684\u8868\u5355\u3002
username.required=\u5fc5\u987b\u5f55\u5165\u7528\u6237\u540d\u3002
password.required=\u5fc5\u987b\u5f55\u5165\u5bc6\u7801\u3002
captcha.required=\u5fc5\u987b\u5f55\u5165\u9a8c\u8bc1\u7801\u3002
error.authentication.captcha.bad=\u5f55\u5165\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e\u3002
           

打開WEB-INF/webflow/login/login-webflow.xml,修改設定viewLoginForm,同時增加設定captchaValidate

<view-state id="viewLoginForm" view="casLoginView" model="credential">
        <binder>
            <binding property="username" required="true"/>
            <binding property="password" required="true"/>
            <binding property="captcha" required="true"/>
        </binder>
        <on-entry>
            <set name="viewScope.commandName" value="'credential'"/>

            <!--
            <evaluate expression="samlMetadataUIParserAction" />
            -->
        </on-entry>
        <transition on="submit" bind="true" validate="true" to="captchaValidate"/>
    </view-state>

    <action-state id="captchaValidate">
        <evaluate expression="authenticationViaFormAction.validatorCode(flowRequestContext, flowScope.credential, messageContext)" />
        <transition on="error" to="generateLoginTicket" />
        <transition on="success" to="realSubmit" />
    </action-state>
           

重新開機一下Tomcat,看看效果

CAS學習記錄2--CAS Server的改造

七、小結

Server端的改造完成了,後面開始Client端的設定

繼續閱讀