一、cas 简介:
1.1 Architecture 图
1.2 系统组件
- cas 服务端和cas 客户端是通过多种协议交互的物理组件。即由服务端和客户端两部分构成。(The CAS server and clients comprise the two physical components of the CAS system architecture that communicate by means of various protocols.)
- cas 服务端:cas server 是基于spring框架的 java servlet,主要是为了实现用户校验以及授权访问CAS嵌入的设备(cas客户端)。
- CAS Clients 在通常的使用中,这个字段“CAS client”有两个特点:
- 一个CAS client 是任意一个集成CAS通过一个支持协议可以和CAS server进行交互的应用。
- CAS client 也是一个能够通过授权协议 (e.g. CAS, SAML, OAuth)实现和CAS server交互的软件包,这个软件包可以是基于不同软件开发平台的应用的。
1.3 cas service 重要的三个方面:
Web (Spring MVC/Spring Webflow)
Ticketing
Ticket Registry 存储可持久化的ticket仓库
ExpirationPolicyAuthentication
1.4 cas 协议
- TGT(Ticket Granting Ticket)存储在TGCcookie中的(票证授予票证)表示用户的SSO会话。
- ST作为URL中的get参数,传输的(服务票据)代表CAS服务器为特定用户授予集成cas应用程序的访问权限。
1.5 Web flow diagram
web flow 详解
1、 用户 user首次 在浏览器 Browser 访问集成cas的系统(Protected App)后,被filter拦截,因为未创建有效的session会话,校验cookie必然不通过。即用户未被授权,
url路径:https://app.example.com/
2、 重定向到cas 登录页面。实现完成单点登陆后跳转至https://app.example.com/
url:形如 https://cas.example.com/cas/login?servie=https://app.example.com/
3、 进入到登录form页面,完成登录 cas server 获取到用户信息,去数据库(ldap、SQL server、oracle、plsql)验证用户信息验证通过后,会在cas server 和 brower 之间创建一个有效session。此处会生成两个ticket(TGT、ST),Ticket Granting Ticket(TGT),保存用户与单点登陆的登录状态。
set-cookie:CASTGC=TGT-2345678
url:https://app.example.example.com/?ticket=ST-12345678
4、service ticket(ST) 服务票据,由TGT颁发给Protected App 认证票据,Protected App拿着该票据可以前往cas server 验证,返回xml状态信息。此处之所以将st拼接在url路径的后面,是因为cookie不能支持跨域,需要通过get请求传参方式将当前的登录状态告诉 Protected App
url: https://cas.example.com/serviceValidate?service=https://app.example.com/?ticket=ST-12345678
5、 验证通过后,会与browser创建一次有效的session。
6、 访问第二个系统的流程同上。
1.5 cas logout 单点登出
cas 协议 /logout端点负责销毁当前的SSO会话
二、 部署测试
1、 测试版本 cas 6.0.0(待续)
测试环境:jdk 11 tomcat 9 gradle
部署记录:
1 | 1. git 地址 https://github.com/apereo/cas-overlay-template.git |
2、 测试版本 cas 4.0.0
部署记录:
使用war包,位于路径:cas-server-4.0.0-release\cas-server-4.0.0\modules\cas-server-webapp-4.0.0.war
测试环境 jdk 7 tomcat 8
参考 https://blog.csdn.net/zzq900503/article/details/54693267
官网下载zip 解压
默认登录用户 casuser Mellon
github https://github.com/apereo/cas/releases
去掉默认的https 设置:
修改一下3个配置文件
服务端配置:
WEB-INF/deployerConfigContext.xml (修改https–>http)
1
2
3
4<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient"
p:requireSecure="false"
/>WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml(修改https–>http)
1
2
3
4
5
6<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure="false"
p:cookieMaxAge="-1"
p:cookieName="CASTGC"
p:cookiePath="/cas"
/>WEB-INF/spring-configuration/warnCookieGenerator.xml(修改https–>http)
1 | <bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator" |
去掉安全警告提示
删除 cas-server-webapp-4.0.0/WEB-INF/view/jsp/default/ui/casLoginView.jsp中(修改https–>http):1
2
3
4<div id="msg" class="errors">
<h2>Non-secure Connection</h2>
<p>You are currently accessing CAS over a non-secure connection. Single Sign On WILL NOT WORK. In order to have single sign on work, you MUST log in over HTTPS.</p>
</div>启动tomcat 访问路径 localhost:8080/cas-server-webapp-4.0.0
更改数据源测试:
6.1 数据源:mysql:
添加所需以来jar包
c3p0-0.9.1.2.jar、cas-server-support-jdbc-4.0.0.jar、mysql-connector-java-8.0.12.jar修改文件 cas-server-webapp-4.0.0/WEB-INF/deployerConfigContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--
| The authentication manager defines security policy for authentication by specifying at a minimum
| the authentication handlers that will be used to authenticate credential. While the AuthenticationManager
| interface supports plugging in another implementation, the default PolicyBasedAuthenticationManager should
| be sufficient in most cases.
+-->
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
<constructor-arg>
<map>
<!--
| IMPORTANT
| Every handler requires a unique name.
| If more than one instance of the same handler class is configured, you must explicitly
| set its name to something other than its default name (typically the simple class name).
-->
<entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
<entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver"/>
<!--<entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />-->
</map>
</constructor-arg>
<!-- Uncomment the metadata populator to allow clearpass to capture and cache the password
This switch effectively will turn on clearpass.
<property name="authenticationMetaDataPopulators">
<util:list>
<bean class="org.jasig.cas.extension.clearpass.CacheCredentialsMetaDataPopulator"
c:credentialCache-ref="encryptedMap" />
</util:list>
</property>
-->
<!--
| Defines the security policy around authentication. Some alternative policies that ship with CAS:
|
| * NotPreventedAuthenticationPolicy - all credential must either pass or fail authentication
| * AllAuthenticationPolicy - all presented credential must be authenticated successfully
| * RequiredHandlerAuthenticationPolicy - specifies a handler that must authenticate its credential to pass
-->
<property name="authenticationPolicy">
<bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
</property>
</bean>
<!-- Required for proxy ticket mechanism. -->
<bean id="proxyAuthenticationHandler"
class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient" p:requireSecure="false"/>
<!--
| TODO: Replace this component with one suitable for your enviroment.
|
| This component provides authentication for the kind of credential used in your environment. In most cases
| credential is a username/password pair that lives in a system of record like an LDAP directory.
| The most common authentication handler beans:
|
| * org.jasig.cas.authentication.LdapAuthenticationHandler
| * org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler
| * org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler
| * org.jasig.cas.support.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler
-->
<!-- <bean id="primaryAuthenticationHandler"
class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
<property name="users">
<map>
<entry key="casuser" value="Mellon"/>
</map>
</property>
</bean>-->
<!-- Required for proxy ticket mechanism -->
<bean id="proxyPrincipalResolver"
class="org.jasig.cas.authentication.principal.BasicPrincipalResolver" />
<!--
| Resolves a principal from a credential using an attribute repository that is configured to resolve
| against a deployer-specific store (e.g. LDAP).
-->
<bean id="primaryPrincipalResolver"
class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
<property name="attributeRepository" ref="attributeRepository" />
</bean>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:driverClass="com.mysql.jdbc.Driver"
p:jdbcUrl="jdbc:mysql://47.96.126.248:3306/cas?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=CONVERT_TO_NULL"
p:user="root"
p:password="930326" />
<!-- 密码加密方式-->
<bean id="passwordEncoder"
class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"
c:encodingAlgorithm="MD5"
p:characterEncoding="UTF-8" />
<bean id="dbAuthHandler"
class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"
p:dataSource-ref="dataSource"
p:sql="select pswd from dcc_user where name=?"
/>
<!--p:passwordEncoder-ref="passwordEncoder"-->
<!--
Bean that defines the attributes that a service may return. This example uses the Stub/Mock version. A real implementation
may go against a database or LDAP server. The id should remain "attributeRepository" though.
+-->
<bean id="attributeRepository" class="org.jasig.services.persondir.support.StubPersonAttributeDao"
p:backingMap-ref="attrRepoBackingMap" />
<util:map id="attrRepoBackingMap">
<entry key="uid" value="uid" />
<entry key="eduPersonAffiliation" value="eduPersonAffiliation" />
<entry key="groupMembership" value="groupMembership" />
</util:map>
<!--
Sample, in-memory data store for the ServiceRegistry. A real implementation
would probably want to replace this with the JPA-backed ServiceRegistry DAO
The name of this bean should remain "serviceRegistryDao".
+-->
<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"
p:registeredServices-ref="registeredServicesList" />
<util:list id="registeredServicesList">
<bean class="org.jasig.cas.services.RegexRegisteredService"
p:id="0" p:name="HTTP and IMAP" p:description="Allows HTTP(S) and IMAP(S) protocols"
p:serviceId="^(https?|imaps?)://.*" p:evaluationOrder="10000001" />
<!--
Use the following definition instead of the above to further restrict access
to services within your domain (including sub domains).
Note that example.com must be replaced with the domain you wish to permit.
This example also demonstrates the configuration of an attribute filter
that only allows for attributes whose length is 3.
-->
<!--
<bean class="org.jasig.cas.services.RegexRegisteredService">
<property name="id" value="1" />
<property name="name" value="HTTP and IMAP on example.com" />
<property name="description" value="Allows HTTP(S) and IMAP(S) protocols on example.com" />
<property name="serviceId" value="^(https?|imaps?)://([A-Za-z0-9_-]+\.)*example\.com/.*" />
<property name="evaluationOrder" value="0" />
<property name="attributeFilter">
<bean class="org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter" c:regex="^\w{3}$" />
</property>
</bean>
-->
</util:list>
<bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" />
<bean id="healthCheckMonitor" class="org.jasig.cas.monitor.HealthCheckMonitor" p:monitors-ref="monitorsList" />
<util:list id="monitorsList">
<bean class="org.jasig.cas.monitor.MemoryMonitor" p:freeMemoryWarnThreshold="10" />
<!--
NOTE
The following ticket registries support SessionMonitor:
* DefaultTicketRegistry
* JpaTicketRegistry
Remove this monitor if you use an unsupported registry.
-->
<bean class="org.jasig.cas.monitor.SessionMonitor"
p:ticketRegistry-ref="ticketRegistry"
p:serviceTicketCountWarnThreshold="5000"
p:sessionCountWarnThreshold="100000" />
</util:list>
</beans>
6.2 数据源oracle, cas加解密方式支持md5,aes256,其他加密方式需要实现自定义加解密。数据源oracle、自定义加解密实现,方式一(已实现,todo:添加连接池技术)需要实现AbstractUsernamePasswordAuthenticationHandler,方式二 ,实现PasswordEncoder,可使用cas引入的连接池配置(提示登录名或密码错误,具体原因不详):
引入 jar包:spring-jdbc-3.1.1.RELEASE、ojdbc6-11.2.0.3、cas-server-support-jdbc-4.2.7、commons-dbcp-1.2.1、MyPasswordEncoder-1.0-SNAPSHOT
MyPasswordEncoder 工程构建:
pom.xml:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-core</artifactId>
<version>3.5.2</version>
<exclusions>
<exclusion>
<groupId>javax.xml</groupId>
<artifactId>xmldsig</artifactId>
</exclusion>
<exclusion>
<groupId>org.jasig.service.persondir</groupId>
<artifactId>person-directory-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
用户校验handler: RsCasDaoAuthenticationHandler1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101package com.dist.sso;
import com.dist.util.EncoderUtil;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* @author Zhangyp
* @description
* @date Created in 9:33 2018/9/8
* @modified By
*/
//TODO 未使用连接池技术
public final class RsCasDaoAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
protected boolean authenticateUsernamePasswordInternal(UsernamePasswordCredentials credentials) throws AuthenticationException {
// 标志位
boolean bool = false;
String username = credentials.getUsername().trim();
String password = credentials.getPassword();
// 取得MD5加密后的字符串
password = EncoderUtil.aes256(password).trim();
System.out.println("开始CAS认证方式 RsCasDaoAuthenticationHandler......");
System.out.println("userName:" + username);
System.out.println("password:" + password);
// 连接数据库
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Properties properties = new Properties();
// 使用ClassLoader加载properties配置文件生成对应的输入流
InputStream in = RsCasDaoAuthenticationHandler.class.getClassLoader().getResourceAsStream("datasource.properties");
// 使用properties对象加载输入流
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
//获取key对应的value值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
String url = properties.getProperty("url");
/*String user = "dgpcd";
String pwd = "pass";
String url = "jdbc:oracle:thin:@172.24.16.1:1521:orcl";*/
try {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
conn = DriverManager.getConnection(url, user, pwd);
String sql = "select count(*) from dcc_user where loginname='" + username + "' and loginpwd='" + password + "'";
System.out.println(">>>> sql : " + sql);
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
if (rs != null && rs.next()) {
int i = rs.getInt(1);
System.out.println("》》》》计数统计:" + i);
if (i > 0) {
// 只要有对应的一条记录通过,就返回true
bool = true;
}
}
} catch (SQLException sql) {
sql.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return bool;
}
}
EncoderUtil.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32package com.dist.util;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class EncoderUtil {
public static String aes256(String content) {
return aes256Base64(content);
}
public static String aes256Base64(String content) {
byte[] encryptData = aes256(SecurityDefine.AESKey.getBytes(), content.getBytes());
Base64 encoder = new Base64();
return new String(encoder.encode(encryptData));
}
public static byte[] aes256(byte[] keyArray, byte[] contentArray) {
SecretKeySpec secretKeySpec = new SecretKeySpec(keyArray, "AES");
try {
Cipher cipher = Cipher.getInstance(secretKeySpec.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] result = cipher.doFinal(contentArray);
return result;
} catch (Exception e) {
System.err.println(e.getMessage());
}
return null;
}
}
SecurityDefine.java
1 | package com.dist.util; |
配置cas deployerConfigContext.xml
修改datasource1
2
3
4
5
6
7
8
9<property name="authenticationHandlers">
<list>
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient"
p:requireSecure="${dasc.cas.enableHttps}"
/>
<bean class="com.dist.sso.RsCasDaoAuthenticationHandler"></bean>
</list>
</property>
客户端配置:
pom.xml 配置:
1
2
3
4
5<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.5.0</version>
</dependency>web.xml 配置:
1 | <listener> |