# CAS标准版接入
# 4.1 背景
- 项目名称:统一身份认证1.0
- 项目开发:杭州半云科技有限公司
# 4.2 对接说明
本文档详细的描述子应用系统通过接口接入“统一身份认证1.0”,解决传统认证机制中存在的一些问题,实现单点登陆(Singe Sign - On)。
本系统基于Apereo CAS的单点登录协议,并针对原有系统缺点进行了改进,使得认证和授权分离,并且减轻了认证服务器的负担,减少了网络传输,方便用户快速访问资源。本文档适用于应用系统研发人员阅读。
平台基于CAS6.6开发,只在原协议基础上扩展,完全兼容原协议。
开源项目地址:https://github.com/apereo/cas
官方在线文档:https://apereo.github.io/cas/6.6.x/index.html
官方实现客户端接入参考文档:https://apereo.atlassian.net/wiki/spaces/CASC/pages/103252551/Official+Clients
对接地址:http://server/cas (请向管理员索要)
# 4.3 协议说明 to do(文档接口完善)
协议标准文档:https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-Specification.html#cas-protocol-30-specification
# 4.4 SDK下载
- Java (opens new window) https://github.com/apereo/java-cas-client
- ASP NET (opens new window) https://github.com/apereo/dotnet-cas-client
- php (opens new window) https://github.com/apereo/phpCAS
# 4.5 JAVA EE,JSP应用对接
# 4.5.1 拷贝统一身份认证客户端所需jar包到应用中
将CAS客户端所需的jar包casclient.jar、cas-client-core-3.2.1.jar拷贝到 projectName\WebRoot\WEB-INF\lib目录内。(注:projectName为应用的项目名) 各个jar包参考代码示例。
# 4.5.2 配置文件WEB.XML
<?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:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<!-- 统一退出过滤器 -->
<filter>
<filter-name>SSO Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<!-- 该过滤器负责用户的认证工作,必须启用它 -->
<filter>
<filter-name>SSOFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://cas.server:port/cas/login</param-value>
</init-param>
<init-param>
<!-- 本系统的UIA -->
<param-name>serverName</param-name>
<param-value>http://localhost:8088</param-value>
</init-param>
<init-param>
<param-name>renew</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>gateway</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://cas.server:8080/cas</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 本系统的UIA -->
<param-name>serverName</param-name>
<param-value>http://localhost:8088</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>/proxyCallback</param-value>
</init-param>
<init-param>
<param-name>proxyCallbackUrl</param-name>
<param-value>http://localhost:8088/proxyCallback</param-value>
</init-param>
</filter>
<!--该过滤器负责实现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>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<!-- cas mapping s -->
<filter-mapping>
<filter-name>SSO Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/proxyCallback</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>SSOFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<!-- 要拦截的本系统登录界面 -->
<filter-name>SSOFilter</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 HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
</web-app>
# 4.5.3 代码示例
点击下载
index.jsp
<%@page import="java.net.URLDecoder"%>
<%@page import="java.util.*"%>
<%@page import="org.jasig.cas.client.util.AbstractCasFilter"%>
<%@page import="org.jasig.cas.client.validation.Assertion"%>
<%@page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<style type="text/css">
td{
border: 1px solid;
}
</style>
</head>
<body>
<%Assertion assertion=
(Assertion)
session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
/*获取用户扩展信息
*扩展信息由UIA的SSO配置决定
*/
Map<String, Object> map = assertion.getPrincipal().getAttributes();
%>
<table style="border: 1px solid;width: 100%;">
<tr>
<td style="width: 10%;">唯一标识(工号)</td>
<td><%= assertion.getPrincipal().getName()%></td>
</tr>
<tr>
<td >用户昵称</td>
<td><%= decode((String)map.get("nickName")) %></td>
</tr>
<tr>
<td >邮箱</td>
<td><%= decode((String)map.get("email")) %></td>
</tr>
<tr>
<td >手机号</td>
<td><%= decode((String)map.get("phonenumber")) %></td>
</tr>
<tr>
<tr>
<td><a href="localhost:8080/cas/logout?service=http://localhost:8088/logout.action">退出</a>
</tr>
</table>
<%!
private List<Map<String,String>> parseStringToList(String str) throws Exception {
List<Map<String,String>> list = new ArrayList<Map<String,String>>();
if(str == null || str.equals("")){
return list;
}
str = decode(str);
String[] array = str.split("-");
for (String subArray : array) {
String[] keyResult = subArray.split(",");
Map<String,String> map = new HashMap<String, String>();
for (String subResult : keyResult) {
String[] value = subResult.split(":");
map.put(value[0], value[1]);
}
list.add(map);
}
return list;
}
private String decode(String str) throws Exception{
if(str != null){
str = URLDecoder.decode(str,"UTF-8");
}
return str;
}
%>
</body>
</html>
# 4.6 SpringBoot前后端对接方式
# 4.6.1 拷贝Maven依赖配置
<dependencies>
<!-- security starter Poms -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- security 对CAS支持 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
</dependency>
<!-- security taglibs -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
</dependencies>
# 4.6.2 配置文件application.properties
app.server.host.url=http://localhost:8081
app.login.url=/login
app.logout.url=/logout
cas.server.host.url=http://cas-server:port/cas
cas.server.host.login_url=${cas.server.host.url}/login
cas.server.host.logout_url=${cas.server.host.url}/logout?service=${app.server.host.url}
# 4.6.3 代码示例
SecurityConfig.java
package cas.client.springboot.demo.security;
import cas.client.springboot.demo.custom.CustomUserDetailsService;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import cas.client.springboot.demo.properties.CasProperties;
@Configuration
@EnableWebSecurity //启用web权限
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CasProperties casProperties;
/**定义认证用户信息获取来源,密码校验规则等*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.authenticationProvider(casAuthenticationProvider());
//inMemoryAuthentication 从内存中获取
//auth.inMemoryAuthentication().withUser("chengli").password("123456").roles("USER")
//.and().withUser("admin").password("123456").roles("ADMIN");
//jdbcAuthentication从数据库中获取,但是默认是以security提供的表结构
//usersByUsernameQuery 指定查询用户SQL
//authoritiesByUsernameQuery 指定查询权限SQL
//auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);
//注入userDetailsService,需要实现userDetailsService接口
//auth.userDetailsService(userDetailsService);
}
/**定义安全策略*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//配置安全策略
//.antMatchers("/","/hello").permitAll()//定义/请求不需要验证
.anyRequest().authenticated()//其余的所有请求都需要验证
.and()
.logout()
.permitAll()//定义logout不需要验证
.and()
.formLogin();//使用form表单登录
http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint())
.and()
.addFilter(casAuthenticationFilter())
.addFilterBefore(casLogoutFilter(), LogoutFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class);
//http.csrf().disable();
}
/**认证的入口*/
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
/**指定service相关信息*/
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(casProperties.getAppServerUrl() + casProperties.getAppLoginUrl());
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
/**CAS认证过滤器*/
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl());
return casAuthenticationFilter;
}
/**cas 认证 Provider*/
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService());
//casAuthenticationProvider.setUserDetailsService(customUserDetailsService()); //这里只是接口类型,实现的接口不一样,都可以的。
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("casAuthenticationProviderKey");
return casAuthenticationProvider;
}
/*@Bean
public UserDetailsService customUserDetailsService(){
return new CustomUserDetailsService();
}*/
/**用户自定义的AuthenticationUserDetailsService*/
@Bean
public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> customUserDetailsService(){
return new CustomUserDetailsService();
}
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl());
}
/**单点登出过滤器*/
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(casProperties.getCasServerUrl());
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
/**请求单点退出过滤器*/
@Bean
public LogoutFilter casLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl());
return logoutFilter;
}
}
# 4.7 ASP.NET
# 4.7.1 拷贝Net CasClient客户端到应用中
将CAS客户端所需的文件DotNetCasClient.dll、DotNetCasClient.pdb和DotNetCasClient.xml拷贝到 projectName\bin目录内(请使用附件中的Net CasClien客户端文件)。
并添加引用
# 4.7.2 配置文件web.config
<?xml version="1.0"?>
<configuration>
<!-- 定义casClientConfig配置 -->
<configSections>
<section name="casClientConfig" type="DotNetCasClient.Configuration.CasClientConfiguration, DotNetCasClient"/>
</configSections>
<!--
配置退出Cas参数
caslogoutUrl - Cas退出登录地址
serverName - 退出后返回到指定的地址
-->
<appSettings>
<add key="caslogoutUrl" value="http://ip:port/cas/logout"/>
<add key="serverName" value="http://127.0.0.1:4602"/>
</appSettings>
<connectionStrings/>
<!--
配置Cas客户端参数
casServerLoginUrl - CAS服务器登录表单的URL。
casServerUrlPrefix - CAS服务器应用程序的根
serverName - 登陆成功后返回到执行地址
ticketValidatorName - 验证使用协议名称。有效值为Cas10,Cas20和Saml11
singleSignOut - 允许应用程序接收时,用户的SSO会话结束发送CAS单点登录了消息。这将导致在该应用中,用户的会话被破坏。默认值是true
serviceTicketManager - 服务票据管理器使用存储在CAS服务器进行验证,撤销和单点登录的全力支持返回的票。如果没有配置的一票管理,这些功能将被禁用
renew - 强制用户访问该应用程序之前重新验证到CAS。这提供了额外的安全性,在使用性的成本,因为它可以有效地禁用SSO这种应用。默认值是false。
-->
<casClientConfig
casServerLoginUrl="http://ip:port/cas/login"
casServerUrlPrefix="http://ip:port/cas/"
serverName="http://127.0.0.1:4602"
ticketValidatorName="Cas20"
singleSignOut="true"
serviceTicketManager="CacheServiceTicketManager"
redirectAfterValidation="true" />
<system.web>
<!--
如果出现其他单点登录应用退出,当前应用不退出的情况
当前应用使用的是.net ifreamwork框架环境是4.0,
第一种情况:在<system.web>中加入 <httpRuntime requestValidationMode="2.0"/>
第二种情况:如果你的网站程序本身是net 2.0环境开发的,但放到了VS2010软件里运行,也会出现这种情况,你可以把运行解决方案切换成net2.0即可
-->
<httpRuntime requestValidationMode="2.0"/>
<compilation debug="true"></compilation>
<!--
配置ASP.NET表单验证部分,以便它指向的casClientConfig节casServerLoginUrl属性定义的CAS服务器的登录网址。这是非常重要的中科院登录URL是在这两个位置是一样的。
注意:这里请勿配置path参数
-->
<authentication mode="Forms">
<forms
loginUrl="http://ip:port/cas/login"
timeout="30"
defaultUrl="~/Default.aspx"
cookieless="UseCookies"
slidingExpiration="true" />
</authentication>
<!--禁止匿名登录-->
<authorization>
<deny users="?" />
</authorization>
<httpModules>
<add name="DotNetCasClient" type="DotNetCasClient.CasAuthenticationModule,DotNetCasClient"/>
</httpModules>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules>
<remove name="DotNetCasClient"/>
<add name="DotNetCasClient" type="DotNetCasClient.CasAuthenticationModule,DotNetCasClient"/>
</modules>
</system.webServer>
</configuration>
# 4.7.3 代码示例
# 4.8 PHP
# 4.8.1 整合CAS PHP客户端代码到项目中
把CAS PHP Client的源码copy到/includes下,如下图结构: 图中红色框标注文件和目录为CAS PHP Client的全部源码。 修改CAS/client.php,将其中的https改为http
文件参考下文 代码示例
# 4.8.2 配置文件config.php
<?php
//CAS Server 主机名
define('CAS_SERVER_HOSTNAME', 'cas-ip');
//CAS Server 端口号
define('CAS_SERVER_PORT', cas-port);
//CAS Server应用名
define('CAS_SERVER_APP_NAME', '/cas');
//退出登录后返回地址
define('LOGOUT_ADDRESS', 'http://xxx.xxx.xxx.xxx');
?>
# 4.8.3 Client端的设置示例
如果出现其他单点登录应用退出,当前应用不退出的情况。请按照以下方式处理:
- 第一种情况:当前应用使用的是.net ifreamwork框架环境是4.0在
<system.web>
中加入<httpRuntime requestValidationMode="2.0"/>
。 - 第二种情况:如果你的网站程序本身是net 2.0环境开发的,但放到了VS2010软件里运行,也会出现这种情况,你可以把运行解决方案切换成net2.0即可。
# 4.8.4 代码示例
index.php
<?php
// 引入配置文件
include 'config.php';
// 引入CAS Client项目
include '/includes/CAS.php';
// 初始化CAS客户端参数
phpCAS::client(CAS_VERSION_2_0,CAS_SERVER_HOSTNAME,CAS_SERVER_PORT,CAS_SERVER_APP_NAME,true);
// 不使用SSL服务校验
phpCAS::setNoCasServerValidation();
// 这里会检测服务器端的退出的通知,就能实现php和其他语言平台间同步登出了
phpCAS::handleLogoutRequests();
// 判断是否已经访问CAS验证,true-获取用户信息,false-访问CAS验证
if(phpCAS::checkAuthentication()){
/**
*获取用户的唯一标识信息
*由UIA的配置不同可分为两种:
*(1)学生:学号;教工:身份证号
*(2)学生:学号;教工:教工号
**/
$userid=phpCAS::getUser();
// 获取登录用户的扩展信息
// 用户姓名
$name = phpCAS::getAttribute("nickName");
// 电话号码
$phone = phpCAS::getAttribute("phonenumber");
// 邮件
$email = phpCAS::getAttribute("email");
// 获取所有扩展参数信息
$attribute = phpCAS::getAttributes();
}else{
// 访问CAS的验证
phpCAS::forceAuthentication();
}
// 退出登录
if(isset($_REQUEST['a'])){
if($_REQUEST['a'] == "logout"){
$param=array("service"=>LOGOUT_ADDRESS);
phpCAS::logout($param);
}
}
function toArray($data){
$result = array();
if(isset($data)){
$arrays = explode("-",$data);
foreach ($arrays as $temp) {
$_array = explode(",",$temp);
$arrayName = array();
if(count($_array)>0){
foreach ($_array as $_temp) {
$_array1 = explode(":",$_temp);
if(count($_array1)>0&&isset($_array1[0])&&isset($_array1[1])){
$arrayName[$_array1[0]] = $_array1[1];
}
}
if(count($arrayName)>0){
array_push($result, $arrayName);
}
}
}
}
return $result;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>测试单点登录</title>
<style type="text/css">
table td{
padding-left: 10px;
height:30px;
}
body{
font-family: "微软雅黑";
}
</style>
</head>
<body>
<table border="1" style="width:80%;margin-left:auto;margin-right:auto;margin-top:40px;margin-bottom:40px;">
<tr>
<th style="width:20%;">参数名称:</th>
<th style="widht:80%;">参数值</th>
</tr>
<tr>
<td>登陆唯一ID:</td><td><?php echo $userid;?></td>
</tr>
<tr>
<td>用户名称:</td><td><?php echo $name;?></td>
</tr>
<tr>
<td>用户电话:</td><td><?php echo $phone;?></td>
</tr>
<tr>
<td>用户邮箱:</td><td><?php echo $email;?></td>
</tr>
<tr>
<td colspan="2"><a href="http://ip:port/?a=logout">退出登录</a></td>
</tr>
</table>
</body>
</html>
← 三、认证中心能力介绍 五、Oauth2接入 →