springmvc--sso单点登录cas统一身份认证器
开发环境
- maven
- idea
- Windows 10
- JDK 1.8+
域名解析的配置
这里通过SwitchHosts来实现:以管理员身份打开
前两个:两个客户端应用的域名
后一个:是服务端的域名。
CAS服务器安装
下载服务端的overlay包:
https://github.com/apereo/cas-overlay-template
下载下来的压缩包:
服务端项目构建:
命令行状态下,在文件夹cas-overlay-template-master中运行命令:
build.cmd package
注意:
- 该操作是在windows下cmd下的操作。
- CAS 5.2.3的编译jdk版本必须是1.8以上
- Maven必须安装了和配置ok,这里使用的是3.5.2,主要maven不要配置私服
- 构建时必须联网,因为要下载相关依赖。
稍等片刻(视网络环境)执行完毕后,在target下找到cas.war备用:
准备好tomcat软件,将cas.war拷贝到tomcat的webapp目录中,
启动tomcat即可
出现下面就ok了使用浏览器访问
ip:port/cas/login 登录页面 默认 用户名密码(casuser/Mellon)
去除https协议认证
打开application.properties文件,在文件最后添加一行(注释是我自己写的,可以不要):
##
#不使用加密传输https协议,仅使用http,默认是打开状态
#
cas.tgc.secure=false
注册允许访问的客户端
第一步在ServiceId的值中加入“|http”。
提示:这里的值支持正则表达式,因此,也可以限定某个或某些具体的域名或ip才能访问服务端。如^http://localhost.*$,是只允许本地所有的客户端的访问。
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps|http)://.*",
"name" : "HTTPS and IMAPS",
"id" : 10000001,
"description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
"evaluationOrder" : 10000
}
第二步:修改application.properties文件,告知 CAS 服务端从本地加载服务定义文件的位置等,将下面的配置复制到配置文件的最后:
#开启识别json文件,默认false
cas.serviceRegistry.initFromJson=true
相关属性说明(了解):
- @class:必须为org.apereo.cas.services.RegisteredService的实现类
- serviceId:对服务进行描述的表达式,可用于匹配一个或多个 URL 地址
- name: 服务名称
- id:全局唯一标志
- evaluationOrder:定义多个服务的执行顺序
修改完成后,需要重启服务端才能生效!
注面销页
ip:port/cas/logout
打开服务端的\cas\WEB-INF\classes\application.properties文件,在文件最后添加一行
##
#Logout 退出
#
#是否允许退出后跳转到登录页面并指定再次登录后跳转的页面,默认是false,即登录不重定向到service参数指定的页面
cas.logout.followServiceRedirects=true
退出标签链接格式
<a href="http://CAS服务器IP:port/cas/logout?service=http://客户端IP:port">退出</a>
当点击之后回跳到登录页面
客户端搭建
客户端的开发可以参考官方文档:
https://apereo.github.io/cas/5.2.x/integration/CAS-Clients.html
官方提供了多种语言的客户端示例,当然这里选择下载Java的客户端,点击网址(直达:https://github.com/cas-projects/cas-sample-java-webapp)进入下载页面
下载下来的压缩包:
参考上节解压的客户端演示项目中的pom.xml、web.xml来配置当前项目。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!--
<context-param>
<param-name>renew</param-name>
<param-value>true</param-value>
</context-param>
-->
<!--修改五个位置-->
<!--退出过滤器-->
<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>
<!--cas服务器地址 如果突出他会跳转 -->
<param-value>http://ip:port/cas</param-value>
</init-param>
</filter>
<!--退出监听器-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!--认证过滤器-->
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<!--<filter-class>org.jasig.cas.client.authentication.Saml11AuthenticationFilter</filter-class>-->
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<!--如果要登录的话,会跳转到一个统一身份认证的服务器的一个登录界面-->
<param-value>http://ip:port/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<!--登录成功后悔自动跳转回该客户端的地址,一般首页-->
<param-value>http://ip:port</param-value>
</init-param>
</filter>
<!--校验过滤器-->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<!--<filter-class>org.jasig.cas.client.validation.Saml11TicketValidationFilter</filter-class>-->
<filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<!--校验的cas服务器地址-->
<param-value>http://ip:port/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<!--客户端地址,在服务器登录后,自动转回客户端默认地址-->
<param-value>http:/ip:port</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>useSession</param-name>
<param-value>true</param-value>
</init-param>
<!--
<init-param>
<param-name>acceptAnyProxy</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>proxyReceptorUrl</param-name>
<param-value>/sample/proxyUrl</param-value>
</init-param>
<init-param>
<param-name>proxyCallbackUrl</param-name>
<param-value>https://mmoayyed.unicon.net:9443/sample/proxyUrl</param-value>
</init-param>
-->
<init-param>
<param-name>authn_method</param-name>
<param-value>mfa-duo</param-value>
</init-param>
</filter>
<!--重新封装request对象的过滤器-->
<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 Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>
index.jsp
</welcome-file>
</welcome-file-list>
</web-app>
pom依赖
<dependencies>
<!-- cas客户端核心 -->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.4.1</version>
</dependency>
<!-- slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编译版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- tomcat7插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 端口 -->
<port>9002</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
测试页面
<%@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>B CAS 淘宝</title>
</head>
<body>
<h1>B CAS 淘宝</h1>
<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>
log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout
SSO(Single Sign On)
中文翻译为单点登录,它是目前主流的企业业务整合的解决方案之一,SSO的目标是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。即:使用SSO整合后,只需要登录一次就可以进入多个系统,而不需要重新登录,提供更好的用户体验,降低了安全的风险和管理的消耗。
CAS(Central Authentication Service)
中文翻译为统一身份认证服务或*身份服务,CAS就是一套方案。它由服务端和客户端组成,实现了SSO,并且容易进行企业应用的集成
专业术语:
TGT(Ticket Granting ticket,票据授予的票据):cas用户登录成功后,服务端会生成一个大票据,封装了TGC和用户信息,可以生成票据的票据,该票据存放于CAS服务端中,可以签发ST默认在内存缓存中(InMemoryService)。默认有效期为120分钟
TGC (Ticket-granting cookie, 票据授予的Cookie):它是由CAS服务端生成,CAS服务器会将TGC写入浏览器的cookie中, 数据结构是个数组对象。所以TGC是一个cookie,用来存放用户身份相关信息(加密过的),是CAS服务器对于浏览器是否信任的凭证,默认有效期为120分钟,浏览器关闭时销毁。
CAS服务端TGC
ST(Service ticket, 服务票据):是TGT签发给某个客户端服务的一次性票据,数据结构是一个长字符串。
当客户端服务拿到该票据后,要立刻回过头来找CAS服务端验证真伪(TGT有能力验证)。验证通过,则可以访问该客户端请求的资源;验证不通过,则抛出异常,提示不识别票据。
1)这个服务票据是临时一次性的,有效期非常的短,也就是说,服务端发出ST,客户端拿到ST,客户端要立刻到服务端去校验,如果中间出现某个环节时间过长,则票据验证失败。
2)客户端到服务端校验ST后,服务端返回的是个XML,内容参考如下:
最终封装成一个信任对象(Assertion)保存到客户端浏览器的session中:
1)第一次访问客户端
浏览器访问客户端,他会校验是否有session 信任对象(Assertion),
没有就会重定向到CAS服务器上登录页面
,登陆成功,生成相应的票据,生成TGC写入Cookie,
TGT签发ST(给客户端一个临时票据然后客户端拿着票据再重新请求CAS服务比对票据,对比之后票据就清除了),
最终封装成一个信任对象(Assertion)保存到客户端的session中;
如果票据不对则会抛出异常
流程图
2)使用浏览器访问另一个客户端,他会校验是否有session 信任对象-->没有-->重定向到CAS服务器从cookie获取TGC,根据TGC找到TGT然后颁发ST凭证,携带ST到浏览器,浏览器携带ST凭证到CAS服务器比对验证,得到验证结果生成信息对像保存到客户端session中;然后重定向到目标资源;
3)重新访问在浏览器已经打开已经认证过的客户端,他会校验客户端是否有session信任对象-->有-->访问目标资源