概念:
单点登录( Single Sign-On , 简称 SSO)是目前较为流行的服务于企业业务整合的解决方案之一,在多个应用系统中,用户只需要登录一次,就可以访问所有相互信任的应用系统。
主要包括两部分:CAS Server和CAS Client
如何搭建CAS Server服务端?,如下所示:
1.windows系统下,修改hosts 文件的方法添加域名,建立自定义域名和IP之前映射关系。
具体文件位置:C:\Windows\System32\drivers\etc\hosts
2.安装JDK8安装配置,这里不再描述.
3.使用keytool工具,配置安全证书 (需要处于jdk的bin目录下,例如: D:\java\jdk1.8\bin)
生成证书:keytool -genkey -alias ssodemo -keyalg RSA -keysize 1024 -keypass michaelpwd -validity 365 -keystore D:\SSO\keytool\ssodemo.keystore -storepass michaelpwd
- 截图中需要输入的姓名和上面hosts文件中配置的一致;
- keypass 和 storepass 两个密码要一致,否则下面tomcat 配置https 访问失败;
导出证书:keytool -export -alias ssodemo -keystore D:\SSO\keytool\ssodemo.keystore -file D:\SSO\keytool\ssodemo.crt -storepass michaelpwd
客户端导入证书:keytool -import -keystore D:\java\jdk1.8\jre\lib\security\cacerts -file D:\SSO\keytool\ssodemo.crt -alias ssodemo
默认密码是:changeit
4.配置CAS Server的tomcat,我这里使用的是tomcat8,apache-tomcat-8.5.57-windows-x64.zip
在文件 conf/server.xml文件中修改内容如下:
验证https配置,双击startup.bat,启动tomcat,访问https://demo.micmiu.com:8443即可
5.部署CAS-Server
下载cas-overlay-template-5.2.zip源码,编译后生成cas.war包,并复制到第4步的Tomcat\webapps下,启动tomcat。在浏览器地址栏输入:https://demo.micmiu.com:8443/cas/login,出现如下所示:
看到上述页面表示CAS-Server已经部署成功。
其中修改登录的用户名/密码文件(已经修改成admin/admin)
路径如下:tomcat-ca\webapps\cas\WEB-INF\classes\application.properties
默认是不支持HTTP,如果需要支持HTTP,则需要修改cas.war的配置文件,增加http
路径:webapps\cas\WEB-INF\classes\services\HTTPSandIMAPS-10000001.json
6.部署CAS-client
下载相关jar包,cas-client-core-3.6.1.jar,在web.xml配置文件中增加filter配置
有关cas-client的web.xml修改的详细说明见官网介绍:
如果在同一台物理机上,记得修改CAS- client的tomcat端口,避免与CAS-server的tomcat端口重复。
7.基本的测试
预期流程: 打开CAS- client url —-> 跳转CAS-server 验证 —-> 显示CAS- client的应用
提供CAS的官网:CAS | Apereo
8.CAS client参数的多种策略配置方式,一共有5中配置方式
如果不配置,默认DEFAULT
第一种:默认方式(DEFAULT),实现类:LegacyConfigurationStrategyImpl
先webXmlConfigurationStrategy策略,如果获取不到参数,再jndiConfigurationStrategy策略
也就是说先第二种方式获取,如果获取不到用第三种方式获取。
第二种:web.xml配置方式(WEB_XML),实现类:WebXmlConfigurationStrategyImpl
最常见的配置方式,在web.xml中配置casServerLoginUrl,casServerUrlPrefix,serverName
这里就不多说了。
第三种:JNDI配置方式(JNDI),实现类:JndiConfigurationStrategyImpl
jndi(Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API
不常用,有兴趣的自行百度。
需要在web.xml中增加如下配置:
<context-param>
<param-name>configurationStrategy</param-name>
<param-value>JNDI</param-value>
</context-param>
第四种:配置文件方式(PROPERTY_FILE),比较推荐,实现类:PropertiesConfigurationStrategyImpl
需要在web.xml中增加如下配置:
<context-param>
<param-name>configurationStrategy</param-name>
<param-value>PROPERTY_FILE</param-value>
</context-param><context-param>
<param-name>configFileLocation</param-name>
<param-value>/opt/cas.properties</param-value>
</context-param>
这里configFileLocation的值需要绝对路径(classpath:cas.properties不可以),因为源码中无法解析classpath字符串,
源码的实现方式如图所示:
,在cas.properties文件中配置casServerLoginUrl,casServerUrlPrefix,serverName等参数
serverName=http://xxx:8080
casServerLoginUrl=https://xxx:8443/cas/login
casServerUrlPrefix=https://xxx:8443/cas
第五种:设置系统参数方式(SYSTEM_PROPERTIES),比较推荐,
需要在web.xml中增加如下配置:
<context-param>
<param-name>configurationStrategy</param-name>
<param-value>SYSTEM_PROPERTIES</param-value>
</context-param>
可增加上下文监听器,将CAS client所需要的的参数设置到系统环境中去
代码如下:
先读取cas.properties文件内容,然后利用servlet上下文监听器,配置casServerLoginUrl,casServerUrlPrefix,serverName等参数信息到系统环境中。
或者
配置项目启动的系统参数
在选择项目按右键->Run as->Run as configurations…;在VM arguments后追加-DXXX=****(-D不能省略),这样就可以通过 System.getProperty(“XXX”)获取****了
或者
通过tomca服务器加载时候定义变量
这里还分windows版本和linux版本,以tomcat7为例。
1)windows环境下:
编辑tomcat7主目录\bin 下的catalina.bat,在第二行定义XXXX,然后就可以System.getProperty(“XXX”)获取****了。
2)linux环境下:
不要像windows设置一样,不要加set 血的教训。
到此开源cas-client-core-3.6.1.jar包 CAS client集成结束。
/*********************************************************自定义实现过滤器***********************************************************/
1.AuthenticationFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
HttpSession httpSession = req.getSession();
String reqUrl = req.getRequestURL().toString();
//http://127.0.0.1:8090/GBaseConsole/test
// String[] urlArry = reqUrl.split("://");
// System.out.println("==========urlArry==========");
// System.out.println(urlArry[0]);
// System.out.println(urlArry[1]);
// System.out.println("===========================");
// String ssoClient = urlArry[0] + "://" + urlArry[1].substring(0, urlArry[1].indexOf("/"))
// + req.getServletContext().getContextPath();
// 给Constants中SSO_CLIENT赋值
//Constants.SSO_CLIENT = reqUrl;
System.out.println("ssoClient="+reqUrl);
String ticket = (String) req.getParameter("ticket");
System.out.println("AuthenticationFilter,ticket="+ticket);
String backurl = request.getParameter("backUrl");
System.out.println("AuthenticationFilter,backurl: " + backurl);
if(backurl==null||"".equals(backurl)){
String url = req.getQueryString();
if(url != null && !"".equals(url) && url.lastIndexOf("backUrl")!=-1){
backurl = url.substring(url.lastIndexOf("backUrl")+8);
}
}
System.out.println("backurl="+backurl);
UserSession userSession = (UserSession)httpSession.getAttribute(Constants.SESSION_KEY);
if((ticket == null || ticket.equals("")) && userSession == null){ //没有登录,重定向到登录页面
String encodeUrl = URLEncoder.encode(reqUrl + "?backUrl="+backurl, "utf-8");
String redirectUrl = "https://" + Constants.SSO_IAM_SERVER + ":" + Constants.SSO_IAM_PORT + "/cas/login?service=" + encodeUrl;
System.out.println("AuthenticationFilter=========redirectUrl: " + redirectUrl);
resp.sendRedirect(redirectUrl);
}else{
chain.doFilter(request, response);
}
}
2.Cas20ProxyReceivingTicketValidationFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ByteArrayOutputStream bos = null;
BufferedReader br = null;
try {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
HttpSession session = req.getSession();UserSession userSession = (UserSession) session.getAttribute(Constants.SESSION_KEY);
// userId不为空表示是进入到LoginedServlet的跳转
String userId = (String) req.getParameter("userId");
String userName = (String) req.getParameter("userName");
if (userSession != null || (userId != null && userName != null)) {
chain.doFilter(request, response);
return;
}// 没有session,则SSO验证
String reqUrl = req.getRequestURL().toString();
String ticket = req.getParameter("ticket");
String backUrl = req.getParameter("backUrl");
System.out.println("cas20 filter backUrl=" + backUrl);
if(backUrl!=null && !"".equals(backUrl)){
backUrl = URLEncoder.encode(backUrl, "utf-8");
}
System.out.println("Cas20ProxyReceivingTicketValidationFilter====SSO_CLIENT="+ reqUrl);
String encodeUrl = URLEncoder.encode(reqUrl + "?backUrl="+backUrl, "utf-8");
String validateUrl = "https://" + Constants.SSO_IAM_SERVER + ":" + Constants.SSO_IAM_PORT
+ "/cas/serviceValidate?ticket=" + ticket + "&service=" + encodeUrl;
System.out.println("validateUrl= " + validateUrl);
HttpsURLConnection httpsUrlConn = null;
//创建SSLContext对象,并使用我们指定的信任管理器初始化
SSLContext sc = createSSLContext();
URL url = new URL(validateUrl);
httpsUrlConn = (HttpsURLConnection)url.openConnection();
httpsUrlConn.setSSLSocketFactory(sc.getSocketFactory());
// httpsUrlConn.setDoInput(true);
// httpsUrlConn.setDoOutput(true);
// httpsUrlConn.setUseCaches(false);
httpsUrlConn.setRequestProperty("accept", "*/*");
httpsUrlConn.setRequestProperty("connection", "Keep-Alive");
httpsUrlConn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
int resp_status = httpsUrlConn.getResponseCode();
if(resp_status == HttpsURLConnection.HTTP_OK){
br = new BufferedReader(new InputStreamReader(httpsUrlConn.getInputStream(),"utf-8"));
bos = new ByteArrayOutputStream();
String line;
while((line=br.readLine())!=null){
bos.write(line.getBytes("utf-8"));
}
String respXML = new String(bos.toByteArray(),"utf-8");
System.out.println("respXML="+respXML);
String error = XmlUtils.getTextForElement(respXML, "authenticationFailure");
System.out.println("=========authenticationFailure="+error);if (error != null && !error.equals("")) { // 认证失败
System.out.println("=========authenticationFailure="+error);
throw new TicketValidationException(error);
} else { // 认证成功
// 获取用户名称
userId = XmlUtils.getTextForElement(respXML, "userId");
userName = XmlUtils.getTextForElement(respXML, "userName");
System.out.println("Cas20ProxyReceivingTicketValidationFilter: userName="+userName);
System.out.println("Cas20ProxyReceivingTicketValidationFilter: userId="+userId);
System.out.println("Cas20ProxyReceivingTicketValidationFilter: backUrl="+backUrl);
// if(userName!=null && !"".equals(userName)){
// userName = URLEncoder.encode(userName, "utf-8");
// }
// if(userId!=null && !"".equals(userId)){
// userId = URLEncoder.encode(userId, "utf-8");
// }
// if(backUrl!=null && !"".equals(backUrl)){
// backUrl = URLEncoder.encode(backUrl, "utf-8");
// }
UserSession user = new UserSession();
user.setUserId(userId);
user.setUserName(userName);
user.setTicket(ticket);
req.getSession().setAttribute(Constants.SESSION_KEY, user);
System.out.println("cas filter user============ "+userId+", "+userName+", "+ticket);
// chain.doFilter(req, resp);
// String redirectUrl = "/set/session?ticket=" + ticket + "&userId=" + userId + "&userName=" + userName + "&backUrl="+backUrl;
String redirectUrl = reqUrl+"?ticket=" + ticket + "&backUrl="+backUrl;
System.out.println("validate success,redirectURL:"+redirectUrl);
resp.sendRedirect(redirectUrl);
}
}
} catch (Exception e) {
System.out.println("Cas20ProxyReceivingTicketValidationFilter errors");
e.printStackTrace();
} finally{
if(bos!=null){
bos.close();
}
if(br!=null){
br.close();
}
}
}@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Cas20ProxyReceivingTicketValidationFilter过滤器初始化");
}
private static SSLContext createSSLContext() {
try {
TrustManager[] wrapped = new TrustManager[1];
wrapped[0] = new X509ExtendedTrustManagerChild();
SSLContext context = SSLContext.getInstance("SSL");
context.init(null, wrapped, new SecureRandom());
return context;
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("NoSuchAlgorithmException." + e.getMessage());
} catch (KeyManagementException e) {
throw new IllegalArgumentException("KeyManagementException." + e.getMessage());
}
}private static class X509ExtendedTrustManagerChild extends X509ExtendedTrustManager{
@Override
public void checkClientTrusted(X509Certificate[] arg0,
String arg1)
throws java.security.cert.CertificateException
{
// TODO Auto-generated method stub
}@Override
public void checkServerTrusted(X509Certificate[] arg0,
String arg1)
throws java.security.cert.CertificateException
{
// TODO Auto-generated method stub
}@Override
public X509Certificate[] getAcceptedIssuers()
{
// TODO Auto-generated method stub
return new X509Certificate[0];
}@Override
public void checkClientTrusted(X509Certificate[] arg0,
String arg1, Socket arg2)
throws java.security.cert.CertificateException
{
// TODO Auto-generated method stub
}@Override
public void checkClientTrusted(X509Certificate[] arg0,
String arg1, SSLEngine arg2)
throws java.security.cert.CertificateException
{
// TODO Auto-generated method stub
}@Override
public void checkServerTrusted(X509Certificate[] arg0,
String arg1, Socket arg2)
throws java.security.cert.CertificateException
{
// TODO Auto-generated method stub
}@Override
public void checkServerTrusted(X509Certificate[] arg0,
String arg1, SSLEngine arg2)
throws java.security.cert.CertificateException
{
// TODO Auto-generated method stub
}
}