原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/81318649 ©王赛超
之前在服务端整合了数据库,也整合了shiro,我们一直是在服务端玩,登录跳转到登录成功页面,没啥意思,今天我们来将服务端和 客户端整合,使不同的客户端使用cas登录。cas服务端还是基于之前的整合shiro版本。
环境概述
ip | 域名 | 对应服务 |
---|---|---|
127.0.0.1 | server.cas.com | CAS服务器 |
127.0.0.1 | app1.cas.com | CAS客户端1 |
127.0.0.1 | app2.cas.com | CAS客户端2 |
配置域名
在/etc/hosts
中增加如下配置:
127.0.0.1 server.cas.com
127.0.0.1 app1.cas.com
127.0.0.1 app2.cas.com
service配置(服务端)
客户端接入 CAS 首先需要在服务端进行注册,否则客户端访问将提示“未认证授权的服务”警告:
需求:对所有https和http请求的service进行允许认证,在resources/services下新建文件HTTPSandIMAPS-10000001.json
,这个文件是我从cas源代码同路径下拷贝过来的。
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps|http)://.*",
"name" : "测试客户端",
"id" : 10000001,
"description" : "这是一个测试客户端的服务,所有的https或者http访问都允许通过",
"evaluationOrder" : 10000
}
注意:services目录中可包含多个 JSON 文件,其命名必须满足以下规则:${name}-${id}.json
,id必须为json文件中内容id一致。
对其中属性的说明如下,更多详细内容见官方文档-Service-Management。
● @class:必须为org.apereo.cas.services.RegisteredService的实现类
● serviceId:对服务进行描述的表达式,可用于匹配一个或多个 URL 地址
● name: 服务名称
● id:全局唯一标志
● description:服务描述,会显示在默认登录页
● evaluationOrder:定义多个服务的执行顺序
修改application.properties
配置好service之后,根据官方文档-service-registry,还需修改 application.properties 文件告知 CAS 服务端从本地加载服务定义文件
#注册客户端
cas.serviceRegistry.initFromJson=true
cas.serviceRegistry.watcherEnabled=true
cas.serviceRegistry.schedule.repeatInterval=120000
cas.serviceRegistry.schedule.startDelay=15000
cas.serviceRegistry.managementType=DEFAULT
cas.serviceRegistry.json.location=classpath:/services
cas.logout.followServiceRedirects=true
启动时,打印如下日志,说明服务注册成功
2018-07-31 18:49:38,611 WARN [org.apereo.cas.services.ServiceRegistryInitializer] - <Skipping [Apereo] JSON service definition as a matching service [Apereo] is found in the registry>
2018-07-31 18:49:38,611 WARN [org.apereo.cas.services.ServiceRegistryInitializer] - <Skipping [测试客户端] JSON service definition as a matching service [测试客户端] is found in the registry>
客户端配置
导入证书
网上说必须保证客户端证书和服务端证书是同一个证书,不然就会报错,我因为是在同一台机器,所以就没有进行这一步操作。
sudo keytool -import -file /Users/wangsaichao/Desktop/tomcat.cer -alias tomcat -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/security/cacerts -storepass changeit
搭建客户端
在官方文档中提供了 CAS Java 客户端样例,即cas-sample-java-webapp。下载项目导入idea
修改pom.xml
<!-- 为了测试方便,两个客户端都是使用http协议,引入tomcat插件,方便测试,默认是jetty。另一个客户端将端口改为 8082 -->
<!-- tomcat7 plugin -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8081</port>
<uriEncoding>UTF-8</uriEncoding>
<server>tomcat7</server>
<path>/</path>
</configuration>
</plugin>
修改web.xml
这里给的例子是client1的,如果是client2只需要将 app1.cas.com:8081改为 app2.cas.com:8082
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>cas-app</display-name>
<!-- ========================单点登录开始 ======================== -->
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 -->
<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>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://server.cas.com:8443/cas</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器用于实现单点登录功能 -->
<filter>
<filter-name>CAS Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://server.cas.com:8443/cas/login</param-value>
<!-- 使用的CAS-Server的登录地址,一定是到登录的action -->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://app1.cas.com:8081</param-value>
<!-- 当前Client系统的地址 -->
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://server.cas.com:8443/cas</param-value>
<!-- 使用的CAS-Server的地址,一定是在浏览器输入该地址能正常打开CAS-Server的根地址 -->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://app1.cas.com:8081</param-value>
<!-- 当前Client系统的地址 -->
</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()
或者request.getUserPrincipal().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>
<!-- ========================单点登录结束 ======================== -->
</web-app>
修改index.jsp
为了看出效果,在两个客户端的index.jsp添加一些标签,和两个客户端互相跳转的路径,下面只给出客户端1的完整实例
<%@page contentType="text/html" %>
<%@page pageEncoding="UTF-8" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.util.List" %>
<%@ page import="org.jasig.cas.client.authentication.AttributePrincipal" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>CAS Example Java Web App</title>
</head>
<body>
<h1>当前为客户端1</h1>
<h2><a href="http://app1.cas.com:8081/">客户端1</a></h2>
<h2><a href="http://app2.cas.com:8082/">客户端2</a></h2>
<p>A sample web application that exercises the CAS protocol features via the Java CAS Client.</p>
<hr>
<p><b>Authenticated User Id:</b> <a href="logout.jsp" title="Click here to log out"><%= request.getRemoteUser() %>
</a></p>
<%
if (request.getUserPrincipal() != null) {
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
final Map attributes = principal.getAttributes();
if (attributes != null) {
Iterator attributeNames = attributes.keySet().iterator();
out.println("<b>Attributes:</b>");
if (attributeNames.hasNext()) {
out.println("<hr><table border='3pt' width='100%'>");
out.println("<th colspan='2'>Attributes</th>");
out.println("<tr><td><b>Key</b></td><td><b>Value</b></td></tr>");
for (; attributeNames.hasNext(); ) {
out.println("<tr><td>");
String attributeName = (String) attributeNames.next();
out.println(attributeName);
out.println("</td><td>");
final Object attributeValue = attributes.get(attributeName);
if (attributeValue instanceof List) {
final List values = (List) attributeValue;
out.println("<strong>Multi-valued attribute: " + values.size() + "</strong>");
out.println("<ul>");
for (Object value : values) {
out.println("<li>" + value + "</li>");
}
out.println("</ul>");
} else {
out.println(attributeValue);
}
out.println("</td></tr>");
}
out.println("</table>");
} else {
out.print("No attributes are supplied by the CAS server.</p>");
}
} else {
out.println("<pre>The attribute map is empty. Review your CAS filter configurations.</pre>");
}
} else {
out.println("<pre>The user principal is empty from the request object. Review the wrapper filter configuration.</pre>");
}
%>
</body>
</html>
测试
启动服务端和客户端,此时访问
http://app1.cas.com:8081/
会跳转至
https://server.cas.com:8443/login?service=https%3A%2F%2Fapp1.cas.com%3A8081%2F
输入用户信息,登录成功,返回
http://app1.cas.com:8081/;jsessionid=3EFAC76F253826DB83F73C8EC7432D10
整合中出现的错误
这里首先把出现的错误罗列一下,个跟客户端整合的过程中,也是出现了很多的问题。
1.javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching cas.com found
原因:之前生成证书的时候,用的域名是server.cas.com 在开始使用cas 服务端的时候一直配置的域名是cas.com 其实应该带上server 所以,修改客户端中的cas.com 为 server.cas.com。记得修改/etc/hosts中的域名映射。
2.org.jasig.cas.client.validation.TicketValidationException: No principal was found in the response from the CAS server.
原因:通过debug,发现后台
Cas20ServiceTicketValidator.parseResponseFromServer(Cas20ServiceTicketValidator.java:98)也就是这行代码,解析的时候报错了.具体原因是解析html页面报错,返回的是登录页面,在server验证成功后,应该是templates/protocol/3.0/casServiceValidationSuccess.html页面负责生成与客户端交互的xml信息。但是返回的却是登录页面,问题出在哪里呢?网上很多都是说的中文乱码什么的,我这边不是这个原因。还记得我们上一篇 服务端整合shiro,没错 是shiro权限拦截了,因为我们设置了ShiroFilterFactoryBean 并且配置了/**为 user 就是被拦截了。这个其实是不应该配置 ShiroFilterFactoryBean的.
具体参考 :cas5.3.2单点登录-服务端集成shiro权限认证(五)