CAS(Central Authentication Service) 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目。
从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。
因为4.2现在还不是很稳定,配置4.0的时候,我修改了一部分源码,所以就以4.1.6为例子记录一下CAS Server的配置过程。4.1与4.0的区别是4.1支持用户salt,tgc(TGT cookie加密),log4j2,saml。4.2之前使用的是maven build,bean使用xml配置,而4.2使用的是gradle,大部分配置使用spring自动注入。配置的时候最好是去官网下载源码包,导入后配置。
1.使用JDBC数据库做用户验证
1.1 在cas-server-webapp的pom.xml里面加入jdbc的支持。
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>4.1.6</version>
</dependency>
1.2 数据库连接池,4.2自带了c3p0的连接池,根据情况使用。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
1.3 在cas.properties中写入数据库连接配置。
在applicationContext.xml 添加一个数据源bean
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${cas.database.driverClass}"/>
<property name="url" value="${cas.database.url}"/>
<property name="username" value="${cas.database.user}"/>
<property name="password" value="${cas.database.password}"/>
<property name="minIdle" value="${cas.database.pool.minSize}"/>
<property name="maxActive" value="${cas.database.pool.maxSize}"/>
<property name="validationQuery" value="${cas.database.pool.connectionHealthQuery}"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="1800"/>
<property name="filters" value="stat, wall"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<!-- poolPreparedStatements只支持oracle和mysql5.5+ -->
<property name="poolPreparedStatements" value="true"/>
<property name="maxOpenPreparedStatements" value="150"/>
</bean>
1.4 在 cas.properties中添加jdbc认证查询的sql,因为cas使用的是spring-jdbc,所以语法是spring-jdbc的语法。
# 查询sql,不配置可以根据后面的参数拼接,建议配置上
cas.jdbc.authn.query.encode.sql= select password, salt from user where username=?
# 对数据库取出的密码比对时,对用户输入密码的加密算法,不是passwordEncoder。
cas.jdbc.authn.query.encode.alg= MD5
# 配置静态的全局salt,默认为空
# cas.jdbc.authn.query.encode.salt.static=
# 数据库密码字段,默认password
# cas.jdbc.authn.query.encode.password=
# 数据库salt字段,默认salt
# cas.jdbc.authn.query.encode.salt=
# 数据库存储加密算法加密次数的字段,默认numIterations
# cas.jdbc.authn.query.encode.iterations.field=
# 配置方式的加密次数,默认0(加密一次),实际加密次数=num+1
# cas.jdbc.authn.query.encode.iterations=
配置jdbc认证处理器,在deployerConfigContext.xml
中查询bean primaryAuthenticationHandler
,将class替换成org.jasig.cas.adaptors.jdbc.QueryAndEncodeDatabaseAuthenticationHandler
,这个类是4.1加入的,所以如果你是使用的4.0,又想要salt的支持,可以自己去拷一个或者重写一个。
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryAndEncodeDatabaseAuthenticationHandler"
c:datasource-ref="dataSource" c:sql="${cas.jdbc.authn.query.encode.sql:}" c:algorithmName="${cas.jdbc.authn.query.encode.alg:}"
p:passwordEncoder-ref="passwordEncoder"/>
c:datasource-ref是认证的数据源,c:sql为查询的sql,c:algorithmName用户输入密码加密算法,p:passwordEncoder-ref,对用户输入密码预加密,默认是PlainTextPasswordEncoder,不加密。这个加密在c:algorithmName之前,且不带salt,c:algorithmName是带salt的加密。如果要自己实现passwordEncoder,实现PasswordEncoder接口就可以了。这里就不给出具体实现了。实现以后,记得配置一个passwordEncoder bean,引用你的实现,才能生效。
在数据库中添加一个按你的加密算法生成的用户名,就可以了发布,测试了。
2.CAS的i18n是使用的spring的i18n,如果要自定义cookie的名称,域,在cas-servlet.xml中,找到CookieLocaleResolver,添加两个属性。
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"
p:defaultLocale="zh-CN" p:cookieName="locale" p:cookieDomain=".yourdomain.com" />
3.返回更多的用户信息
deployerConfigContext.xml
中找到attributeRepository
和attrRepoBackingMap
,修改为:
<bean id="attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao"
c:ds-ref="dataSource" c:sql="select * from user where username=?" p:resultAttributeMapping-ref="attrRepoBackingMap"/>
<util:map id="attrRepoBackingMap" >
<entry value="id" key="id" />
<entry value="username" key="username" />
<entry value="phone" key="phone" />
<entry value="nickname" key="nickname" />
<entry value="pic" key="pic" />
<entry value="birthDate" key="birth_date" />
<entry value="sex" key="sex" />
</util:map>
HTTPSandIMAPS-10000001.json
将attributeReleasePolicy
@class修改为返回attrRepoBackingMap
定义的所有属性
org.jasig.cas.services.ReturnAllAttributeReleasePolicy
如果要指定attrRepoBackingMap
的部分属性,则使用下面的配置
"attributeReleasePolicy" : {
"@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy",
"allowedAttributes" : [ "java.util.ArrayList", [ "cn", "mail", "sn" ] ]
}
客户端从request.getUserPrincipal()
获取用户信息
修改\WEB-INF\view\jsp\protocol\2.0\casServiceValidationSuccess.jsp
<cas:user>${fn:escapeXml(principal.id)}</cas:user>
<%-- 添加这段 -- start -->
<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>
<c:if test="${not empty pgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<%-- 添加这段 -- end-->
<c:if test="${fn:length(chainedAuthentications) > 0}">
<cas:proxies>
<c:forEach var="proxy" items="${chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(chainedAuthentications)}" step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
</cas:proxies>
</c:if>
<!DOCTYPE html">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>返回值测试</title>
</head>
<body>
<%
request.setCharacterEncoding("UTF-8");
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
Map attributes = principal.getAttributes();
String userid=(String)attributes.get("id");
String username = (String)attributes.get("username");
String nickname = (String)attributes.get("nickname");
String pic=(String)attributes.get("pic");
%>
<div>飞奔的蜗牛博客:返回值演示</div>
<ul>
<li>userid:<%= userid%></li>
<li>username:<%= username%></li>
<li>nickname:<%= nickname%></li>
<li>pic:<%= pic%></li>
</ul>
</body>
</html>
4. 配置secure cookie
ticketGrantingTicketCookieGenerator.xml
中 ticketGrantingTicketCookieGenerator
bean
添加属性:
<!-- p:cookieSecure 默认false -->
<!-- p:cookieHttpOnly 默认false,设置为true后,ajax无法方法TGT cookie -->
<!-- p:cookieMaxAge 有效期,默认-1 -->
<!-- p:cookiePath cookie路径,默认/cas/ -->
<!-- p:cookieDomain 默认为当前域名,我们需要改成 .yourdomain.com -->
bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure="${cas.cookie.cookieSecure:true}"
p:cookieHttpOnly="${cas.cookie.cookieHttpOnly:true}"
p:cookieMaxAge="${cas.cookie.cookieMaxAge:-1}"
p:cookieName="${cas.cookie.cookieName:CASTGC}"
p:cookiePath="${cas.cookie.path:/}"
p:cookieDomain="${cas.cookie.cookieDomain:}" />
5. 配置cas登录的地址
cas.properties:server.name=youdomain.com
CAS的数据库认证方式就完成了,打包成war包发布。下一次记录cas的audit日志数据库持久化