[关闭]
@zh350229319 2017-10-26T01:26:12.000000Z 字数 12896 阅读 660

第一部分:cas服务器端连接mysql配置

CAS


  因公司要求使用cas服务器,公司原来有一个使用ajax方式进行单点登录的cas服务器,在项目上线后,发现问题太多,改为使用正常方式的cas服务器。
  客户端项目用到了两种权限管理框架,分别是shiro和spring security。
  整个过程分四部分记录修改过程,第一部分记录cas服务器连接mysql配置;第二部分记录cas服务器增加登录验证码;第三部分记录shiro的配置;第四部分记录spring security的配置。

  cas服务器端下载地址:http://developer.jasig.org/cas/
  cas客户端下载地址:http://developer.jasig.org/cas-clients/
  本文使用版本cas-server-4.0.0-release

  试过了最新的4.1.3,使用jetty:run和tomcat都没有本法正常启动,所以使用了此版本。本版本在修改默认语言为中文时发现了一个bug,参考cas-server-4.1.3进行了修复。

(一)下载cas源码,去掉多去的项目

  cas-server包含很多项目,基础只需要cas-server-core、cas-server-webapp、cas-server-webapp-support三个项目。因为需要连接数据库验证用户名和密码,保留了cas-server-support-jdbc项目。
  因为cas使用了log4j,项目运行路径时包含中文路径会报错,但是不影响使用。
  可以使用eclipse的run on server启动cas,要修改Server工程中server.xml中Context节点,保持docBase和path一致,即可启动成功。

  1. <Context docBase="cas-server-webapp" path="/cas-server-webapp" reloadable="true"
  2. source="org.eclipse.jst.j2ee.server:cas-server-webapp"/>

  引入工程后,子项目的pom文件会出现警告,可以在cas-server的pom文件中,把警告提到的插件移动到pluginManagement的plugins节点下。并去掉com.mycila.maven-license-plugin插件。

(二)去掉https

  deployerConfigContext.xml:找到id为proxyAuthenticationHandler的bean,增加p:requireSecure="false"

  1. <bean id="proxyAuthenticationHandler"
  2. class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
  3. p:httpClient-ref="httpClient" p:requireSecure="false" />

ticketGrantingTicketCookieGenerator.xml:
  warnCookieGenerator.xml:p:cookieSecure="true"改为p:cookieSecure="false"

  1. <bean id="ticketGrantingTicketCookieGenerator"
  2. class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  3. p:cookieSecure="false"
  4. p:cookieMaxAge="-1"
  5. p:cookieName="CASTGC"
  6. p:cookiePath="/cas" />
  1. <bean id="warnCookieGenerator"
  2. class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  3. p:cookieSecure="false"
  4. p:cookieMaxAge="-1"
  5. p:cookieName="CASPRIVACY"
  6. p:cookiePath="/cas" />

(三)修改默认语言为中文

  cas-server-4.0.0中有一个bug,需要修改org.jasig.cas.web.viewCasReloadableMessageBundle类中的getMessageInternal()方法

  1. final String filename = this.basenames[i] + "_" + locale.getLanguage();

改为

  1. final String filename = this.basenames[i] + "_" + locale;

  messages_zh_CN.properties中缺少了一些中文提示,因此从cas-server-4.1.3中复制了一个覆盖原文件。
cas-server-4.1.3下载地址:https://github.com/Jasig/cas/tree/v4.1.3

  现在cas-server可以部署到tomcat中并正常启动,在deployerConfigContext.xml的id为deployerConfigContext.xml的bean中保存了默认的用户名和密码。

  1. <bean id="primaryAuthenticationHandler"
  2. class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
  3. <property name="users">
  4. <map>
  5. <entry key="casuser" value="Mellon"/>
  6. </map>
  7. </property>
  8. </bean>

(四)启用cas登出时根据service跳转页面的功能

  因为客户端项目在登出后需要跳转到登录页面,需要开启cas登出后跳转页面的功能。
  cas-servlet.xml:修改id为logoutAction的bean,p:followServiceRedirects="${cas.logout.followServiceRedirects:false}"改为true。

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

(五)增加连接数据库验证用户名和密码的功能

1、修改pom文件,增加jdbc支持

  修改cas-server工程的pom文件,注释掉没有用到的modules。本文连接的数据库是mysql,使用druid连接池。pom增加如下依赖:

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <version>${version.mysql}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.alibaba</groupId>
  8. <artifactId>druid</artifactId>
  9. <version>${version.druid}</version>
  10. </dependency>

  properties节点

  1. <version.druid>1.0.16</version.druid>
  2. <version.mysql>5.1.34</version.mysql>

  修改cas-server-support-jdbc工程的pom文件,增加依赖:

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-jdbc</artifactId>
  4. </dependency>
  5. <!-- 可选,项目需要使用salt加盐算法计算密码才引用 -->
  6. <dependency>
  7. <groupId>org.apache.shiro</groupId>
  8. <artifactId>shiro-core</artifactId>
  9. </dependency>

  修改cas-server-webapp工程的pom文件,增加依赖:

  1. <dependency>
  2. <groupId>org.jasig.cas</groupId>
  3. <artifactId>cas-server-support-jdbc</artifactId>
  4. <version>${project.version}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>com.alibaba</groupId>
  12. <artifactId>druid</artifactId>
  13. </dependency>

2、增加用户名密码验证处理

  cas-server-support-jdbc工程中自带4个验证处理类,可以通过配置的方法来使用。这四个不满足本项目的需求,因此新增了一个自定义验证处理类。
  deployerConfigContext.xml:去掉默认的primaryAuthenticationHandler,增加自定义处理类,并增加druid连接池。

  1. <!-- 自定义验证处理类 -->
  2. <bean id="primaryAuthenticationHandler"
  3. class="org.jasig.cas.adaptors.jdbc.QueryAndSaltDatabaseAuthenticationHandler">
  4. <constructor-arg name="dataSource" ref="dataSource" />
  5. <constructor-arg name="sql" value="${jdbc.selectSQL}" />
  6. <!-- 加密算法 -->
  7. <constructor-arg name="algorithmName" value="${algorithmName}"/>
  8. <property name="numberOfIterations" value="${numberOfIterations}" />
  9. <!-- 数据库中密码字段 -->
  10. <property name="passwordFieldName" value="${passwordFieldName}" />
  11. <!-- 数据库中salt字段 -->
  12. <property name="saltFieldName" value="${saltFieldName}" />
  13. </bean>
  14. <!-- 数据源配置, 使用druid连接池 -->
  15. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
  16. init-method="init" destroy-method="close">
  17. <property name="driverClassName" value="${jdbc.driver}" />
  18. <property name="url" value="${jdbc.url}" />
  19. <property name="username" value="${jdbc.username}" />
  20. <property name="password" value="${jdbc.password}" />
  21. <!-- 配置监控统计拦截的filters -->
  22. <property name="filters" value="${druid.filters}" />
  23. <property name="connectionProperties" value="${druid.connectionProperties}" />
  24. <!-- 配置初始化大小、最小、最大 -->
  25. <property name="initialSize" value="${druid.initialSize}"/>
  26. <property name="minIdle" value="${druid.minIdle}"/>
  27. <property name="maxActive" value="${druid.maxActive}"/>
  28. <!-- 配置获取连接等待超时的时间 -->
  29. <property name="maxWait" value="${druid.maxWait}"/>
  30. <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
  31. <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
  32. <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
  33. <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
  34. <property name="validationQuery" value="${druid.validationQuery}" />
  35. <property name="testWhileIdle" value="${druid.testWhileIdle}" />
  36. <property name="testOnBorrow" value="${druid.testOnBorrow}" />
  37. <property name="testOnReturn" value="${druid.testOnReturn}" />
  38. <!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。-->
  39. <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
  40. <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
  41. </bean>

3、自定义验证处理类:QueryAndSaltDatabaseAuthenticationHandler

  参考cas-server-4.1.3中QueryAndEncodeDatabaseAuthenticationHandler类进行编写,修改authenticateUsernamePasswordInternal()和digestEncodedPassword()。

  1. /*
  2. * Licensed to Jasig under one or more contributor license
  3. * agreements. See the NOTICE file distributed with this work
  4. * for additional information regarding copyright ownership.
  5. * Jasig licenses this file to you under the Apache License,
  6. * Version 2.0 (the "License"); you may not use this file
  7. * except in compliance with the License. You may obtain a
  8. * copy of the License at the following location:
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. package org.jasig.cas.adaptors.jdbc;
  20. import java.security.GeneralSecurityException;
  21. import java.util.Date;
  22. import java.util.Map;
  23. import javax.security.auth.login.AccountNotFoundException;
  24. import javax.security.auth.login.FailedLoginException;
  25. import javax.sql.DataSource;
  26. import javax.validation.constraints.NotNull;
  27. import org.apache.shiro.crypto.hash.ConfigurableHashService;
  28. import org.apache.shiro.crypto.hash.DefaultHashService;
  29. import org.apache.shiro.crypto.hash.HashRequest;
  30. import org.jasig.cas.authentication.AccountDisabledException;
  31. import org.jasig.cas.authentication.HandlerResult;
  32. import org.jasig.cas.authentication.PreventedException;
  33. import org.jasig.cas.authentication.UsernamePasswordCredential;
  34. import org.jasig.cas.authentication.principal.SimplePrincipal;
  35. import org.springframework.dao.DataAccessException;
  36. import org.springframework.dao.IncorrectResultSizeDataAccessException;
  37. /**
  38. * Class that if provided a query that returns a password (parameter of query
  39. * must be username) will compare that password to a translated version of the
  40. * password provided by the user. If they match, then authentication succeeds.
  41. * Default password translator is plaintext translator.
  42. *
  43. * @author Scott Battaglia
  44. * @author Dmitriy Kopylenko
  45. * @author Marvin S. Addison
  46. *
  47. * @since 3.0
  48. */
  49. public class QueryAndSaltDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
  50. private static final String DEFAULT_PASSWORD_FIELD = "password";
  51. private static final String DEFAULT_SALT_FIELD = "salt";
  52. private static final long DEFAULT_ITERATIONS = 1024;
  53. /**
  54. * The Algorithm name.
  55. */
  56. @NotNull
  57. protected final String algorithmName;
  58. /**
  59. * The Sql statement to execute.
  60. */
  61. @NotNull
  62. protected final String sql;
  63. /**
  64. * The Sql statement to execute.
  65. */
  66. @NotNull
  67. protected final String updateSql;
  68. /**
  69. * The Password field name.
  70. */
  71. @NotNull
  72. protected String passwordFieldName = DEFAULT_PASSWORD_FIELD;
  73. /**
  74. * The Salt field name.
  75. */
  76. @NotNull
  77. protected String saltFieldName = DEFAULT_SALT_FIELD;
  78. /**
  79. * The number of iterations. Defaults to 0.
  80. */
  81. protected Long numberOfIterations = DEFAULT_ITERATIONS;
  82. public QueryAndSaltDatabaseAuthenticationHandler(final DataSource dataSource,
  83. final String sql,
  84. final String updateSql,
  85. final String algorithmName) {
  86. super();
  87. setDataSource(dataSource);
  88. this.sql = sql;
  89. this.updateSql = updateSql;
  90. this.algorithmName = algorithmName;
  91. }
  92. /** {@inheritDoc} */
  93. @Override
  94. protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
  95. throws GeneralSecurityException, PreventedException {
  96. final String username = credential.getUsername();
  97. final String encodedPsw = this.getPasswordEncoder().encode(credential.getPassword());
  98. try {
  99. final Map<String, Object> values = getJdbcTemplate().queryForMap(this.sql, username);
  100. //检查用户状态
  101. if(!values.containsKey("status")){
  102. throw new AccountDisabledException();
  103. }
  104. final String status = values.get("status").toString();
  105. if ("00".equals(status)) {
  106. //禁用
  107. throw new AccountDisabledException();
  108. }else if("02".equals(status)){
  109. //删除
  110. throw new AccountNotFoundException();
  111. }
  112. //效验密码
  113. final String digestedPassword = digestEncodedPassword(encodedPsw, values);
  114. if (!values.get(this.passwordFieldName).equals(digestedPassword)) {
  115. throw new FailedLoginException("Password does not match value on record.");
  116. }
  117. int flag = getJdbcTemplate().update(updateSql, new Date(), username);
  118. logger.debug("更新{}最后登录时间:{}", username, flag);
  119. } catch (final IncorrectResultSizeDataAccessException e) {
  120. if (e.getActualSize() == 0) {
  121. throw new AccountNotFoundException(username + " not found with SQL query");
  122. } else {
  123. throw new FailedLoginException("Multiple records found for " + username);
  124. }
  125. } catch (final DataAccessException e) {
  126. throw new PreventedException("SQL exception while executing query for " + username, e);
  127. }
  128. return createHandlerResult(credential, new SimplePrincipal(username), null);
  129. }
  130. /**
  131. * Digest encoded password.
  132. *
  133. * @param encodedPassword the encoded password
  134. * @param values the values retrieved from database
  135. * @return the digested password
  136. */
  137. protected String digestEncodedPassword(final String encodedPassword, final Map<String, Object> values) {
  138. final ConfigurableHashService hashService = new DefaultHashService();
  139. //配置
  140. hashService.setHashAlgorithmName(this.algorithmName);
  141. hashService.setHashIterations(numberOfIterations.intValue());
  142. if (!values.containsKey(this.saltFieldName)) {
  143. throw new RuntimeException("Specified field name for salt does not exist in the results");
  144. }
  145. final String dynaSalt = values.get(this.saltFieldName).toString();
  146. final HashRequest request = new HashRequest.Builder()
  147. .setSalt(dynaSalt)
  148. .setSource(encodedPassword)
  149. .build();
  150. return hashService.computeHash(request).toHex();
  151. }
  152. /**
  153. * Sets password field name. Default is {@link #DEFAULT_PASSWORD_FIELD}.
  154. *
  155. * @param passwordFieldName the password field name
  156. */
  157. public final void setPasswordFieldName(final String passwordFieldName) {
  158. this.passwordFieldName = passwordFieldName;
  159. }
  160. /**
  161. * Sets salt field name. Default is {@link #DEFAULT_SALT_FIELD}.
  162. *
  163. * @param saltFieldName the password field name
  164. */
  165. public final void setSaltFieldName(final String saltFieldName) {
  166. this.saltFieldName = saltFieldName;
  167. }
  168. /**
  169. * Sets number of iterations. Default is 0.
  170. *
  171. * @param numberOfIterations the number of iterations
  172. */
  173. public final void setNumberOfIterations(final Long numberOfIterations) {
  174. this.numberOfIterations = numberOfIterations;
  175. }
  176. }

4、jdbc.properties

  在propertyFileConfigurer.xml修改property-placeholder节点。

  1. <context:property-placeholder location="/WEB-INF/*.properties"/>

  在WEB-INF目录下新增jdbc.properties

  1. jdbc.driver=com.mysql.jdbc.Driver
  2. jdbc.url=jdbc:mysql://192.168.5.127:3306/ucenter?useUnicode=true&characterEncoding=utf-8
  3. jdbc.username=root
  4. jdbc.password=XlYr6hqtsD3YulwHugnpy/FgYywQT2lzuekedA8TtP8kj62MHqa6txQIMIygqD5DVjTv+q5i7V6+yCfqfrE6LA==
  5. druid.filters=config
  6. druid.connectionProperties=config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKRTQGQ4hl08ZckgQKLLvt3oEFfwrl3Jdt23j4Qc9ZMVooK7sgZ/c7GK4fEXI/vTXFVPjV/utcvPbKpF2LhTJCcCAwEAAQ==
  7. #druid connection pool settings
  8. druid.initialSize=10
  9. druid.minIdle=10
  10. druid.maxActive=50
  11. druid.maxWait=60000
  12. druid.timeBetweenEvictionRunsMillis=60000
  13. druid.minEvictableIdleTimeMillis=300000
  14. druid.validationQuery=SELECT 'x'
  15. druid.testWhileIdle=true
  16. druid.testOnBorrow=false
  17. druid.testOnReturn=false
  18. druid.poolPreparedStatements=true
  19. druid.maxPoolPreparedStatementPerConnectionSize=20
  20. jdbc.selectSQL=SELECT `password`,`salt`,`status` FROM ucs_user WHERE username = ?
  21. algorithmName=SHA-1
  22. numberOfIterations=1024
  23. passwordFieldName=password
  24. saltFieldName=salt
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注