CAS 简介
cas是YALE大学发起的一个开源项目, 旨在为web应用系统提供一种可靠的单点登录方法.
它分为server和client端, server端负责对用户的认证工作, client端则负责处理对客户端受保护的资源的访问请求.
CAS的原理,如图:
CAS 名词
Service Ticket: 简称ST, ST是CAS为用户签发的访问某一service的票据。用户访问service时,service发现用户没有ST,则要求用户去CAS获取ST。用户向CAS发出获取ST的请求,如果用户的请求中包含cookie,则CAS会以此cookie值为key查询缓存中有无TGT,如果存在TGT,则用此TGT签发一个ST,返回给用户。用户凭借ST去访问service,service拿ST去CAS验证,验证通过后,允许用户访问资源。
Ticket granting ticket: 简称TGT. 是cas服务器为用户签发的登录票据.拥有了TGT,用户就可以证明自己在CAS成功登录过。TGT封装了Cookie值以及此Cookie值对应的用户信息。用户在CAS认证成功后,CAS生成cookie,写入浏览器,同时生成一个TGT对象,放入自己的缓存,TGT对象的ID就是cookie的值。当HTTP再次请求到来时,如果传过来的有CAS生成的cookie,则CAS以此cookie值为key查询缓存中有无TGT ,如果有的话,则说明用户之前登录过,如果没有,则用户需要重新登录。
Ticket granting cookie: 简称TGC. 这是一个cookie, 是cas服务器放到用户浏览器中用以标识用户身份的cookie.
CAS REST服务部署
stackoverflow参考资料
部署前的准备
- 服务端创建证书
keytool -genkey -alias SomeName -keyalg RSA -keystore d:/your/dir/target.keystore
接着根据提示输入相关信息.在最后,提示输入密码时, 务必记住你输入的密码.
- 服务端导出证书
keytool -export -file d:/your/dir/target.crt -alias SomeName -keystore d:/your/dir/target.keystore
导出时, 会提示你输入刚才创建keystore时的密码.
导出完成后, 生成的target.crt就可以分发给客户端的jdk使用了.
- 客户端导入证书
keytool -import -keystore %JAVA_HOME%/jre/lib/security/cacerts -file d:/your/dir/target.crt -alias SomeName
提示输入密码. 如果出现keytool error: java.io.IOException: Keystore was tampered with, or password was incorrect
错误, 则使用密码changeit.
- 在服务端tomcat服务器上应用证书
-
启动tomcat服务器, 验证SSL是否启用
访问地址
https://localhost:8443/
生成支持rest的cas.war
新建目录, 编写pom.xml, 使用命令mvn clean package
生成cas.war
org.jasig.cas
cas-server
3.4.12
4.0.0
h.usm.my
cas
war
1.0
HUSM CAS Web Application
3.4.12
UTF-8
org.jasig.cas
cas-server-webapp
${cas.version}
war
runtime
org.jasig.cas
cas-server-support-jdbc
${cas.version}
org.jasig.cas
cas-server-integration-restlet
${cas.version}
jar
org.springframework
spring-web
com.h2database
h2
1.4.187
org.hibernate
hibernate-core
${hibernate.core.version}
jar
org.hibernate
hibernate-entitymanager
3.6.0.Final
ja-sig
http://oss.sonatype.org/content/repositories/releases
maven-war-plugin
cas
修改cas.war的web.xml, 填写Rest Servlet
restlet
com.noelios.restlet.ext.spring.RestletFrameworkServlet
1
restlet
/rest/*
修改cas.war的deployerConfigContext.xml, 修改用户名密码的验证方式
注释掉默认的SimpleTestUsernamePasswordAuthenticationHandler
添加新的AuthentitcationHandler
这里使用了数据库来存储用户的帐号与密码.
验证时使用sql进行查询,并对查询获得的password字段值,与使用MD5PasswordEncoder进行加密后的输入密码, 进行比对验证.
相关的dataSource与encoder配置如下:
MD5
通过org.jasig.cas.authentication.handler.PasswordEncoder
接口可实现自定义加密类.
记得添加相应的数据库驱动jar包到lib目录下.
验证
访问 https://localhost:8443/cas
, 输入账密进行网页验证.
CAS Rest的java验证代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import javax.net.ssl.HttpsURLConnection;
public class TestCasRest {
/**
* resolve exception:
* java.security.cert.CertificateException: No name matching localhost found
*/
static {
//for localhost testing only
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
if (hostname.equals("localhost")) {
return true;
}
return false;
}
});
}
public static void main(String... args) throws Exception {
String username = "alpha";
String password = "123";
validateFromCAS(username, password);
}
public static boolean validateFromCAS(String username, String password)
throws Exception {
String url = "https://localhost:8443/cas/rest/tickets";
try {
HttpsURLConnection hsu = (HttpsURLConnection) openConn(url);
String s = URLEncoder.encode("username", "UTF-8") + "="
+ URLEncoder.encode(username, "UTF-8");
s += "&" + URLEncoder.encode("password", "UTF-8") + "="
+ URLEncoder.encode(password, "UTF-8");
System.out.println(s);
OutputStreamWriter out = new OutputStreamWriter(
hsu.getOutputStream());
BufferedWriter bwr = new BufferedWriter(out);
bwr.write(s);
bwr.flush();
bwr.close();
out.close();
String tgt = hsu.getHeaderField("location");
System.out.println("ResponseCode: " + hsu.getResponseCode());
if (tgt != null && hsu.getResponseCode() == 201) {
System.out.println(tgt);
System.out.println("==> TGT is : "
+ tgt.substring(tgt.lastIndexOf("/") + 1));
tgt = tgt.substring(tgt.lastIndexOf("/") + 1);
bwr.close();
closeConn(hsu);
String serviceURL = "http://localhost:8080/CasClient";
String encodedServiceURL = URLEncoder
.encode("service", "utf-8")
+ "="
+ URLEncoder.encode(serviceURL, "utf-8");
System.out.println("Service url is : " + encodedServiceURL);
String myURL = url + "/" + tgt;
System.out.println(myURL);
hsu = (HttpsURLConnection) openConn(myURL);
out = new OutputStreamWriter(hsu.getOutputStream());
bwr = new BufferedWriter(out);
bwr.write(encodedServiceURL);
bwr.flush();
bwr.close();
out.close();
System.out.println("Response code is: "
+ hsu.getResponseCode());
BufferedReader isr = new BufferedReader(new InputStreamReader(
hsu.getInputStream()));
String line;
System.out.println(hsu.getResponseCode());
while ((line = isr.readLine()) != null) {
System.out.println("==> ST is : " + line);
}
isr.close();
hsu.disconnect();
return true;
} else {
return false;
}
} catch (MalformedURLException mue) {
mue.printStackTrace();
throw mue;
} catch (IOException ioe) {
ioe.printStackTrace();
throw ioe;
}
}
static URLConnection openConn(String urlk) throws MalformedURLException,
IOException {
URL url = new URL(urlk);
HttpsURLConnection hsu = (HttpsURLConnection) url.openConnection();
hsu.setDoInput(true);
hsu.setDoOutput(true);
hsu.setRequestMethod("POST");
return hsu;
}
static void closeConn(HttpsURLConnection c) {
c.disconnect();
}
}
Cas client端(非REST请求方式)的配置
在client端工程添加cas-client-core.jar包及相关依赖
org.jasig.cas.client
cas-client-core
3.1.12
修改client端工程的web.xml, 添加cas的过滤器
org.jasig.cas.client.session.SingleSignOutHttpSessionListener
CAS Single Sign Out Filter
org.jasig.cas.client.session.SingleSignOutFilter
CAS Single Sign Out Filter
/*
CASFilter
org.jasig.cas.client.authentication.AuthenticationFilter
casServerLoginUrl
https://sso.wsria.com:8443/cas/login
serverName
http://localhost:10000
CASFilter
/*
CAS Validation Filter
org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
casServerUrlPrefix
https://sso.wsria.com:8443/cas
serverName
http://localhost:10000
CAS Validation Filter
/*
CAS HttpServletRequest Wrapper Filter
org.jasig.cas.client.util.HttpServletRequestWrapperFilter
CAS HttpServletRequest Wrapper Filter
/*
CAS Assertion Thread Local Filter
org.jasig.cas.client.util.AssertionThreadLocalFilter
CAS Assertion Thread Local Filter
/*
AutoSetUserAdapterFilter
AutoSetUserAdapterFilter
com.wsria.demo.filter.AutoSetUserAdapterFilter
AutoSetUserAdapterFilter
/*
其中自定义的AutoSetUserAdapterFilter的代码如下
package com.wsria.demo.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.wsria.demo.entity.account.User;
import com.wsria.demo.service.account.UserManager;
import com.wsria.demo.util.UserUtil;
/**
* 自动根据单点登录系统的信息设置本系统的用户信息
*
* @author 咖啡兔
* @site www.wsria.cn
*
*/
public class AutoSetUserAdapterFilter implements Filter {
/**
* Default constructor.
*/
public AutoSetUserAdapterFilter() {
}
/**
* @see Filter#destroy()
*/
public void destroy() {
}
/**
* 过滤逻辑:首先判断单点登录的账户是否已经存在本系统中,
* 如果不存在使用用户查询接口查询出用户对象并设置在Session中
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// _const_cas_assertion_是CAS中存放登录用户名的session标志
Object object = httpRequest.getSession().getAttribute("_const_cas_assertion_");
if (object != null) {
Assertion assertion = (Assertion) object;
String loginName = assertion.getPrincipal().getName();
User user = UserUtil.getCurrentUser(httpRequest.getSession());
// 第一次登录系统
if (user == null) {
WebApplicationContext wct = WebApplicationContextUtils.getWebApplicationContext(httpRequest
.getSession().getServletContext());
UserManager userManager = (UserManager) wct.getBean("userManager");
user = userManager.findUserByLoginName(loginName);
// 保存用户信息到Session
UserUtil.saveUserToSession(httpRequest.getSession(), user);
}
}
chain.doFilter(request, response);
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
}
}
附注
单点退出
访问https://localhost:8443/cas/logout
即可.
美化CAS服务器界面
修改cas\WEB-INF\view\jsp\default\ui
下相关的jsp文件
在服务端不使用SSL协议
- 修改
%CATALINA_HOME%\conf\server.xml
文件, 关闭Tomcat服务器的SSL端口
- 修改服务端
cas\WEB-INF\deployerConfigContext.xml
文件
- 修改服务端的
cas\WEB-INF\spring-configuration\ticketGrantingTicketCookieGennerator.xml
文件