目录
开发环境:
windows7 64位
jdk1.7.0_17
apache-tomcat-7.0.78-windows-x64
cas-server-webapp-4.0.0.war、cas-client-core-3.2.1.jar、commons-logging.jar
确保本地jdk环境已经搭建好
War包及安装包下载地址:https://pan.baidu.com/s/1hrY4BBm
根据演示需求,用修改hosts 文件的方法添加域名最简单方便(这个非常重要,因为CAS单点登录系统是基于JAVA安全证书的 https 访问, 要使用CAS单点登录必须要配置域名, cas是不能通过ip访问的.),编辑文件C:\Windows\System32\drivers\etc\hosts 在文件末端添加以下信息:
127.0.0.1 server.ywj123.com

注意:这个就是即将用来配置在自己电脑上的第三方单点登录的服务器:cas server。
# 127.0.0.1 client1.ywj123.com
# 127.0.0.1 client2.ywj123.com
这两个客户端是我刚开始测试时配置的,真实项目里都是直接放在本机的Tomcat\webapps下面。
上面3个ip都是127.0.0.1,这是为什么呢?因为我的环境都是在同一台机器,所以ip都是一致的,我们再把不同的服务端和客户端应用,使用不同域名加以区分。一个域名对应一个应用,模拟多端!
CAS支持的两种协议访问方式:
方式一:http协议
1 、 WEB-INF/deployerConfigContext.xml
在
< bean class = "org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref = "httpClient" />
增加参数 p:requireSecure="false" ,是否需要安全验证,即 HTTPS , false 为不采用 如下:
< bean class = "org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref = "httpClient" p:requireSecure= "false" />
2 、 WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml
修改 p:cookieSecure="true" 为 p:cookieSecure=" false " , 即不需要安全 cookie
如下部分:
< bean id = "ticketGrantingTicketCookieGenerator" class = "org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure = " false "
p:cookieMaxAge = "-1"
p:cookieName = "CASTGC"
p:cookiePath = "/cas" />
3 、 WEB-INF\spring-configuration\warnCookieGenerator.xml
修改 p:cookieSecure="true" 为 p:cookieSecure=" false " , 即不需要安全 cookie
结果如下:
< bean id = "warnCookieGenerator" class = "org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure = " false "
p:cookieMaxAge = "-1"
p:cookieName = "CASPRIVACY"
p:cookiePath = "/cas" />
方式二:https协议:
生成安全证书
1:win+R 快捷键打开DOS命令窗口
2:输入以下命令,生成证书
[html] view plaincopy
keytool -genkey -alias ssodemo -keyalg RSA -keysize 1024 -keypass ywj123 -validity 365 -keystore G:\ywj_cas.keystore -storepass ywj123
解释一下:
-alias后面的ssodemo 是我生成证书的别名 ,
-keypass ywj123是我要生成证书的密码,
-storepass ywj123要与上面的-keypass密码相同

【注意】:第一个让你输入的“您的名字与姓氏是什么”,请必须输入在C:\Windows\System32\drivers\etc\hosts文件中加入的服务端的域名。
我这里也就是server.ywj123.com,为何这么做?
首先cas只能通过域名来访问,不能通过ip访问,同时上方是生成证书,所以要求比较严格,所以如果不这么做的话,及时最终按照教程配置完成,cas也可以正常访问,访问一个客户端应用虽然能进入cas验证首页,但是,当输入信息正确后,cas在回调转入你想访问的客户端应用的时候,会出现No subject alternative names present错误异常信息,这个错误也就是在上面输入的第一个问题答案不是域名导致、或者与hosts文件配置的不一致导致
3:导出证书
在cmd窗口继续输入以下命令,导出证书:
[html] view plaincopy
keytool -export -alias ssodemo -keystore G:\ywj_cas.keystore -file G:\ssodemo.crt -storepass ywj123




4:JDK导入证书
由于是本地没有证书,证书是自己生成的,所以,务必将生成的证书导入到jre的证书链中,不然是无法支持CAS认证服务的。
在cmd窗口输入命令:
[html] view plaincopy
keytool -import -keystore "%JAVA_HOME%\jre\lib\security\cacerts" -file G:\ssodemo.crt -alias ssodemo
这里一定要注意一点首先使用%JAVA_HOME%这个路径你是在自己系统环境变量中配置好的jdk路径的,其次一定要主要双""别忘记加了。我就是在这里采坑的,另外还有就是输入这行命令的前提路径是进入你的cd G:\ProgramFiles\Java\jdk1.7.0_17\jre\lib\security这个jdk的安装路径

第一个红色框是我进入的JDK目录第二个我没有加""报的错,第三个是我正确输入路径之后展示的效果就是正确的

紧接着输入y便可JDK导入安全证书成功。
附常用命令:
//查看cacerts中的证书列表:
keytool -list -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
//删除cacerts中指定名称的证书:
keytool -delete -alias ssodemo-keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
//导入指定证书到cacerts:
keytool -import -alias ssodemo -file ssodemo.cer-keystore"%JAVA_HOME%/jre/lib/security/cacerts" -storepasschangeit-trustcacerts
至此,CAS所需的证书环境,已经配置好。
下面,开始我们的CAS服务、Tomcat、客户端的配置及测试访问。
部署CAS-Server相关的Tomcat
1.配置HTTPS
解压apache-tomcat-7.0.78-windows-x64.zip,我本地路径为G:\apache-tomcat-7.0.57,编辑G:\apache-tomcat-7.0.78\conf\server.xml,找到下面片段:
<!--
<Connector executor="tomcatThreadPool"port="8080" protocol="HTTP/1.1"connectionTimeout="20000" redirectPort="8443" />
-->
去掉注释,修改成:
<Connectorport="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
keystoreFile="G:/ ywj_cas.keystore" keystorePass="ywj123"
clientAuth="false" sslProtocol="TLS" />
其中,keystoreFile就是创建证书的路径,keystorePass就是创建证书的密码.
2. 验证HTTPS配置
https://server.ywj123.com:8443/ 出现下面画面,其实这就表明cas服务端tomcat的https配置是没有问题了.

3 .部署CAS-Server
CAS-Server 下载地址: http://www.jasig.org/cas/download
本文以cas-server-webapp-4.0.0.rar为例,解压提取cas-server-webapp-4.0.0.war文件,把改文件copy到G:\apache-tomcat-7.0.57\webapps 目下,并重命名为:cas.war。

这个页面不是原始的页面我修改了背景图片,登录框的位置还没来得及修改,问题不大。
【说明】:此时,CAS只是单独运行,至于登录的用户名和密码是什么,请查看:G:\apache-tomcat-7.0.57\webapps\cas\WEB-INF\deployerConfigContext.xml文件中有这样一段配置:
<bean id="primaryAuthenticationHandler"class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
<property name="users">
<map>
<entry key="casuser" value="Mellon"/>
</map>
</property>
</bean>
代码中key="casuser" value="Mellon就是默认的用户名和密码。
用户名和密码肯定需要和数据库进行交互验证的,,那么,如何配置呢?
【说明】:我本地使用的是oracle数据库。
1、需要将几个jar文件,放到CAS服务的lib目录下
2、修改配置,支持oracle数据库交互验证
编辑G:\apache-tomcat-7.0.57\webapps\cas\WEB-INF\deployerConfigContext.xml文件,你会看到有这样一段配置:



要说明的是,红色标注的内容,我想大家都能看的明白,就是指定数据库驱动和连接信息。其中,sql语句的意思就是,根据用户名获取密码,CAS会根据你页面输入的用户名获取该用户密码,和你输入的密码进行校验,来判断输入是否正确。
其中,sql中的表换成你自己本地的表即可,或者自己手动新建一个表,加入几条测试数据即可。只要根据用户名查询密码即可。
至此,CAS与数据库交互验证的配置已经配置完成,你可以重新访问cas,输入数据库中存在的用户名和密码,来看看效果如何~如果登录成功,说明配置无误。否则,请耐心检查配置是否有问题,jar包是否缺少。
客户端和cas server的交互
1.部署CAS客户端相关的Tomcat

2.在客户端的web.xml中添加配置信息
让客户端应用和CAS服务连接:
编辑G:\apache-tomcat-7.0.57-client1\webapps\examples\WEB-INF\web.xml,在最下面加入如下配置:
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 该过滤器用于实现单点登出功能,可选配置 -->
<filter>
<filter-name>CASSingle Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CASSingle 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>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://server.ywj123.com:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
<filter-name>CASValidation Filter</filter-name>
<filter-class>
org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://server.ywj123.com:8443/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASValidation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责实现HttpServletRequest请求的包裹,比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 -->
<filter>
<filter-name>CASHttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CASHttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。比如AssertionHolder.getAssertion().getPrincipal().getName()。 -->
<filter>
<filter-name>CASAssertion 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>
单点登录整合
基本上到这里所有的配置都结束了,项目可以正常运行了。但是具体情况还有具体说明和解决。因为我在配置这些服务之前其实本身项目里就有自己的登录的页面和流程,所以你不可能让客户登录一次总的cas server页面时还再次登录各个子系统自己网站吧,那就失去了单点登录的意义了,所以我在思考一段时间之后决定是有选择性的绕过子系统的登录流程,不影响子系统的运行做法,具体如下。
1.新建一个cas.jsp文件有如下内容
$(document).ready(function(){
var self= this;
var data= {};
/*data.name= $("#userName").val();
data.password =$("#password").val();
data.name ="admin";
data.password ="1";*/
$.ajax({
url:"/ptgl/LOGIN/login.do",
type:"POST",
data:JSON.stringify(data),
success:function(json){
varresult = JSON.parse($.base64().decode(json));
console.log(result);
if(result.success){
window.open("/ptgl/ptgl/main.do","_self");
}else{
alert("ajax失败!");
}
}
});
});

说明:这里我是做了一个自动登录的AJAX方法,当我选择跳转到这个cas.jsp的时候会自动跳转到之前登录的Controller方法里面。但是也需要在之前的Controller里面改动一些代码以配合当前的登录要求。可以看到我这里的data的参数已经被我注释掉了。这里是不需要的,因为我在后台帮助获取到了。
2后台代码修改
后台如下:
@RequestMapping(value="/login",method=RequestMethod.POST)
public voidlogin(HttpServletRequest request, HttpServletResponse response)
throws UnsupportedEncodingException{
//cas server 中获取登录名
AttributePrincipalprincipal = (AttributePrincipal)request.getUserPrincipal();
StringcasLoginName = principal.getName();
PrintWriterwriter = null;
ResponseVovo = new ResponseVo();
try {
WebApplicationContextwebApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContext servletContext =webApplicationContext.getServletContext();
String bb=(String)servletContext.getAttribute("bb");
request.getSession().setAttribute("key",bb);
String kk=LL.kk(bb);
SimpleDateFormat format = newSimpleDateFormat("yyMMdd");
Stringdatestr = format.format(new Date());
Calendar calendar = Calendar.getInstance();
calendar.setTime(newDate());
calendar.add(Calendar.YEAR, -1);
Date date = calendar.getTime();
String datestr2 = format.format(date);
writer = response.getWriter();
if((kk.compareTo(datestr)<=0)&&(kk.compareTo(datestr2)>=0))
{
response.setHeader(Constant.CROSS_DOMAIN, "*");
// String[]dataOne=request.getParameterValues("data");
// RestServerserver = new RestServer(request, response);
// 此处正是从前台传过来的‘账户名’和‘密码’这两个参数,通过server.getData()获得
// Stringdata = server.getData();
// UserLoginVologinVo = JSONUtil.toBean(data, UserLoginVo.class);
UserLoginVologinVo = new UserLoginVo();
// 此处拿到‘账户名’和‘密码’之后通过loginBusiness.login(loginVo)方法得到用户的所有信息
loginVo.setName(casLoginName);
vo= loginBusiness.login(loginVo);
if(vo.isSuccess())
request.getSession().setAttribute("user", vo.getData());
else
{
vo.setSuccess(false);
vo.setErrorCode(-1000);
vo.setMsg(" 用户名或密码错误!");
}
}else
{
log.info(bb);
}
} catch (Exception e) {
vo.setSuccess(false);
vo.setErrorCode(-1000);
vo.setMsg("错误原因:" +e.getMessage());
}
writer.print(Base64.encodeBase64String(JSONObject.fromObject(vo).toString().getBytes("utf-8")));
}

另:
public ResponseVologin(UserLoginVo loginVo){
ResponseVoresponseVo = new ResponseVo();
SysUserEntityentity = new SysUserEntity();
try{
/*if(StringUtils.isEmpty(loginVo.getName()) ||StringUtils.isEmpty(loginVo.getPassword())){
responseVo.setMsg(ErrorCodeType.USERNAME_OR_PASSWORD_IS_NULL.getErrorMsg());
responseVo.setSuccess(false);
returnresponseVo;
}*/
entity.setUsName(loginVo.getName());
System.out.println("loginVo.getName():"+loginVo.getName()+"====qweqwe=====11111======begin==========="+entity.getUsName());
SysUserEntityuserEntity =
sysUserService.selectUserInfo(entity);
System.out.println("===========22222============"+userEntity.getUsId());
responseVo.setData(userEntity);
//若查询为null,则直接返回不存在该用户
if(userEntity == null){
responseVo.setMsg(ErrorCodeType.USERNAME_IS_NOT_EXIST.getErrorMsg());
responseVo.setSuccess(false);
return responseVo;
}
/* if(StringUtils.isNotBlank(loginVo.getPassword())
&&CommonUtil.md5Encode(loginVo.getPassword()).
equalsIgnoreCase(userEntity.getUsPassword())){
//如果都正确,则将状�?�设置成在线状�??
// sysUserService.updateOnlineStatus(OnlineType.ONLINE.getStatus(),userEntity.getUsTname());
intrememberStatus = 0;
//获取他�?�中记住密码的状�?
responseVo.setData(userEntity);
//若不存在,则说明他的记住状�?�已经过期,或�?�之前没有�?�中�?
if(rememberStatus< 1 && loginVo.getRememberStatus() ==RememberType.REMEMBER.getCode()){
//如果选中了记住密码,则用redis记住他的状�?�,过期时间为一个月[�?个月内免登陆]
}else{
//responseVo.setData(rememberStatus);
returnresponseVo;
}
}else{
responseVo.setSuccess(false);
responseVo.setMsg(ErrorCodeType.PASSWORD_IS_ERROR.getErrorMsg());
}*/
}catch(Exception e){
e.printStackTrace();
}
return responseVo;

可以对照上面的代码把红色方框中代码注释掉逻辑就全部通了,这也就是代码整合时难点的地方,把不同的逻辑理通不出现前后矛盾。
可能遇到的问题总结:
1.question_ONE
此项目中我是把第三方的cas server服务器单独建一个Tomcat进行运行的,要注意端口会和自己的真实项目的端口号会重复,所以只需简单修改一下端口号就可以了。如果不会的话我贴上解决方案:https://blog.youkuaiyun.com/jay_1989/article/details/52870760这篇博客已经说明的很详细了,不会的看一下。
2.question_TWO
如果你仔细观看我的代码及了解cas原理过后的话会发现很多不便的地方,比如配置文件应该单独提取才是,cas 客户端应该获取更多的登录信息等。但是这些都不是最重要的,我发现了一个我自己也没办法解决的问题那就是我发现当登录过后controller的request里面的信息是cas服务端发过来的,所以我能取到登录名,但是我自己新建的jsp文件自动登录功能绕过了此前的子系统的登录页面。后台的controller就无法获取到准确的request了。
就是说原先应该是子系统的request变成了cas服务端的request了,这个后面有时间再去研究。
3.question_THREE
其实子系统的客户端应该本着资源不浪费的情况下不需要再根据用户名去查询对应的信息了。贴上一些有用的博客信息。
https://blog.youkuaiyun.com/hejingyuan6/article/details/45126373
http://www.cnblogs.com/vhua/p/cas_4.html
https://blog.youkuaiyun.com/redstarofsleep/article/details/51145062