本文實現了Web-SSO單點登陸功能,實例中包含三個工程, SSOAuth, SSOWebDemo1, SSOWebDemo2, SSOAuth爲認證系統,使用SSOWebDemo1登陸系統時,要使用SSOAuth進行鑑權, 登陸上Demo1後,系統可實現自動登陸到Demo2的功能css
實例下載:http://www.wisdomdd.cn/Wisdom/resource/articleDetail.htm?resourceId=490html
本文開發工具:java
MyEclipse10: https://pan.baidu.com/s/1eRPzTJGweb
JDK1.7 : https://pan.baidu.com/s/1jHWJSdK算法
1.認識SSOapache
單點登陸(Single Sign On),簡稱爲 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,用戶只須要登陸一次就能夠訪問全部相互信任的應用系統。json
單點登陸的機制實際上是比較簡單的,用一個現實中的例子作比較。頤和園是北京著名的旅遊景點,也是我常去的地方。在頤和園內部有許多獨立的景點,例如「蘇州 街」、「佛香閣」和「德和園」,均可以在各個景點門口單獨買票。不少遊客須要遊玩全部的景點,這種買票方式很不方便,須要在每一個景點門口排隊買票,錢包拿 進拿出的,容易丟失,很不安全。因而絕大多數遊客選擇在大門口買一張通票(也叫套票),就能夠玩遍全部的景點而不須要從新再買票。他們只須要在每一個景點門 口出示一下剛纔買的套票就可以被容許進入每一個獨立的景點。瀏覽器
2.SSO實現機制安全
單點登陸的機制也同樣,以下圖所示,當用戶第一次訪問應用系統1的時候,由於尚未登陸,會被引導到認證系統中進行登陸(1);根據用戶提供的登陸信息,認證系統進行身份效驗,若是經過效驗,應該返回給用戶一個認證的憑據--ticket(2);用戶再訪問別的應用的時候(3,5)就會將這個ticket帶上,做爲本身認證的憑據,應用系統接受到請求以後會把ticket送到認證系統進行效驗,檢查ticket的合法性(4,6)。若是經過效驗,用戶就能夠在不用再次登陸的狀況下訪問應用系統2和應用系統3了。服務器
統一的認證系統並非說只有單個的認證服務器,以下圖所示,整個系統能夠存在兩個以上的認證服務器,這些服務器甚至能夠是不一樣的產品。認證服務器之間要經過標準的通信協議,互相交換認證信息,就能完成更高級別的單點登陸。以下圖,當用戶在訪問應用系統1時,由第一個認證服務器進行認證後,獲得由此服務器產生的ticket。當他訪問應用系統4的時候,認證服務器2可以識別此ticket是由第一個服務器產生的,經過認證服務器之間標準的通信協議(例如SAML)來交換認證信息,仍然可以完成SSO的功能。
3. Web-SSO實現方法
例如用戶在訪問頁面1的時候進行了登陸,客戶端(瀏覽器)的每一個請求都是單獨的鏈接,當客戶再次訪問頁面2的時候,如何才能告訴Web服務器,客戶剛纔已經登陸過了呢?瀏覽器和服務器之間有約定:經過使用cookie技術來維護應用的狀態。Cookie是能夠被Web服務器設置的字符串,而且能夠保存在瀏覽器中。以下圖所示,當瀏覽器訪問了頁面1時,web服務器設置了一個cookie,並將這個cookie和頁面1一塊兒返回給瀏覽器,瀏覽器接到cookie以後,就會保存起來,在它訪問頁面2的時候會把這個cookie也帶上,Web服務器接到請求時也能讀出cookie的值,根據cookie值的內容就能夠判斷和恢復一些用戶的信息狀態。Web-SSO徹底能夠利用Cookie來完成用戶登陸信息的保存,將瀏覽器中的Cookie和上文中的Ticket結合起來,完成SSO的功能。
4. 實例概述
本實例一共有三個Web工程, SSOAuth, SSOWebDemo1, SSOWebDemo2, 訪問SSOWebDemo1的jsp頁面時,demo1的應用程序會判斷是否登陸,若是未登陸,進入登陸頁面,若是已經登陸,顯示登陸人的基本信息; 一樣訪問SSOWebDemo2的jsp頁面時,也會像訪問SSOWebDemo1同樣。 SSOWebDemo1與SSOWebDemo2的跳轉的登陸頁面由SSOAuth提供, 用戶信息由SSOAuth進行校驗
4.1訪問SSOWebDemo1路徑: http://localhost:8080/SSOWebDemo1/test.jsp
若是未登陸成功,跳轉到登陸頁面,見下圖
若是已經登陸,則顯示登陸人信息, 見下圖
同理 SSOWebDemo2
5. 代碼詳解(因爲SSOWebDemo1與SSOWebDemo2類別,下面只講解SSOWebDemo1和SSOAuth)
5.1 SSOWebDemo1的web.xml中配置jsp過濾器,訪問工程中頁面時,會被過濾器類SSOFilter攔截
<?xml version="1.0" encoding="UTF-8"?><web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"><filter><filter-name>SSOFilter</filter-name><filter-class>SSO.SSOFilter</filter-class><!-- <init-param> <param-name>CookieName</param-name> <param-value>SsoTicket</param-value> </init-param> <init-param> <param-name>SSOServiceURL</param-name> <param-value>http://localhost:8080/SSOAuth/SSOAuth</param-value> </init-param> <init-param> <param-name>SSOLoginPage</param-name> <param-value>http://localhost:8080/SSOAuth/login.jsp</param-value> </init-param> --></filter><filter-mapping><filter-name>SSOFilter</filter-name><url-pattern>*.jsp</url-pattern></filter-mapping><session-config><session-timeout> 60 </session-timeout></session-config><welcome-file-list><welcome-file> index.jsp </welcome-file></welcome-file-list></web-app>
5.2 SSOFilter攔截器中獲取瀏覽器中全部cookie信息,判斷是否有須要處理的cookie(根據名稱), 若是沒有,則認爲其未登陸, 頁面跳轉到登陸頁面; 若是cookie列表有須要處理的cookie,調用SSOAuth接口判斷cookie是否有效,若是無效,由跳轉到登陸頁面, 若是有效,SSOAuth返回用戶基本信息給SSOWebDemo1,將用戶信息顯示在頁面上
package SSO;import java.io.IOException;import java.io.PrintStream;import java.io.PrintWriter;import java.io.StringWriter;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.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.httpclient.HttpClient;import org.apache.commons.httpclient.methods.GetMethod;import com.alibaba.fastjson.JSON;public class SSOFilter implements Filter { private FilterConfig filterConfig = null; public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { log("SSOFilter:doFilter()"); HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String result = "failed"; String url = request.getRequestURL().toString(); String qstring = request.getQueryString(); log("SSOFilter: 訪問路徑:" + url); log("SSOFilter: 查詢參數:" + qstring); if (qstring == null) qstring = ""; String cookieValue = ""; //獲取全部cookie信息Cookie[] diskCookies = request.getCookies(); if (diskCookies != null) { for (int i = 0; i < diskCookies.length; i++) { //根據名稱判斷是否含有須要處理的cookieif (diskCookies[i].getName().equals(Constant.CookieName)) { cookieValue = diskCookies[i].getValue(); log("SSOFilter: cookie[" + Constant.CookieName + "," + cookieValue + "]"); //將獲取的cookie值發送給SSOAuth進行校驗, //無效返回failed, 有效返回用戶信息result = SSOService(cookieValue); log("SSOFilter: SSOAuth 返回值:" + result); break; } } } if (result.equals("failed")) { //頁面跳轉到SSOAuth中的登陸頁面, goto對應的url爲登陸成功後跳轉的頁面路徑response.sendRedirect(Constant.SSOLoginPage + "?goto=" + url); } else if (qstring.indexOf("logout") > 1) { log("SSOFilter: logout action!"); logoutService(cookieValue); response.sendRedirect(Constant.SSOLoginPage + "?goto=" + url); } else { //將用戶的json串信息 反轉 爲User對象User user = JSON.parseObject(result, User.class); request.setAttribute("SSOUser", user); Throwable problem = null; try { chain.doFilter(req, res); } catch (Throwable t) { problem = t; t.printStackTrace(); } if (problem != null) { if ((problem instanceof ServletException)) throw ((ServletException) problem); if ((problem instanceof IOException)) throw ((IOException) problem); sendProcessingError(problem, res); } } } public FilterConfig getFilterConfig() { return this.filterConfig; } public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; } public void destroy() { } public void init(FilterConfig filterConfig) { this.filterConfig = filterConfig; if (filterConfig != null) { log("SSOFilter:Initializing filter"); } /*獲取web.xml的配置信息*//*Constant.CookieName = filterConfig.getInitParameter("CookieName"); Constant.SSOServiceURL = filterConfig.getInitParameter("SSOServiceURL"); Constant.SSOLoginPage = filterConfig.getInitParameter("SSOLoginPage");*/ } public String toString() { if (this.filterConfig == null) return "SSOFilter()"; StringBuffer sb = new StringBuffer("SSOFilter("); sb.append(this.filterConfig); sb.append(")"); return sb.toString(); } private void sendProcessingError(Throwable t, ServletResponse response) { String stackTrace = getStackTrace(t); if ((stackTrace != null) && (!stackTrace.equals(""))) { try { response.setContentType("text/html"); PrintStream ps = new PrintStream(response.getOutputStream()); PrintWriter pw = new PrintWriter(ps); pw.print("<html> <head> <title>Error</title> </head> <body> "); pw.print("<h1>The resource did not process correctly</h1> <pre> "); pw.print(stackTrace); pw.print("</pre></body> </html>"); pw.close(); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } elsetry { PrintStream ps = new PrintStream(response.getOutputStream()); t.printStackTrace(ps); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } public static String getStackTrace(Throwable t) { String stackTrace = null; try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); pw.close(); sw.close(); stackTrace = sw.getBuffer().toString(); } catch (Exception ex) { } return stackTrace; } /** * 使用HttpClient向SSOAuth鑑權 * @param cookievalue * @return * @throws IOException */private String SSOService(String cookievalue) throws IOException { String authAction = "?action=authcookie&cookiename="; HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod(Constant.SSOServiceURL + authAction + cookievalue); try { httpclient.executeMethod(httpget); String result = httpget.getResponseBodyAsString(); return result; } finally { httpget.releaseConnection(); } } /** * 退出,讓SSOAuth刪除用戶鑑權信息 */private void logoutService(String cookievalue) throws IOException { String authAction = "?action=logout&cookiename="; HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod(Constant.SSOServiceURL + authAction + cookievalue); try { httpclient.executeMethod(httpget); httpget.getResponseBodyAsString(); } finally { httpget.releaseConnection(); } } public void log(String msg) { this.filterConfig.getServletContext().log("[工程1]" + msg); }}
5.3 基本常量信息
package SSO;public class Constant { public static String CookieName = "SsoTicket"; public static String SSOServiceURL = "http://localhost:8080/SSOAuth/SSOAuth"; public static String SSOLoginPage = "http://localhost:8080/SSOAuth/login.jsp"; public static final boolean debug = true;}
5.4 SSOWebDemo1中訪問的jsp頁面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%><%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ page import="SSO.User" %><%String contextPath = request.getContextPath();%><!DOCTYPE html><html><head><title>系統1</title><link rel="STYLESHEET" type="text/css" href="css.css"></head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0"><br><div><h1>系統1登陸成功</h1><table border="0" cellpadding="0" cellspacing="0" width="748"><tr><td height="100" width="300"><h3>Welcome!</h3><h4>姓名: <%=((User)request.getAttribute("SSOUser")).getUserName()%></h4><h4>密碼: <%=((User)request.getAttribute("SSOUser")).getPassword()%></h4><h4>年齡: <%=((User)request.getAttribute("SSOUser")).getAge()%></h4><h4>地址: <%=((User)request.getAttribute("SSOUser")).getAddress()%></h4><h3>Success Access Web SSO Demo1</h3><br><h3><a href="test.jsp?action=logout">LogOut</a></h3></td></tr></table></div></body></html>
6.下面講解SSOAuth
6.1 web.xml中配置servlet,用於向SSOWebDemo1,SSOWebDemo2提供接口調用,
接口功能: SSOWebDemo1根據cookie值到SSOAuth中獲取登陸人基本信息;
SSOAuth自身的登陸退出操做
29<?xml version="1.0" encoding="UTF-8"?><web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"><servlet><servlet-name>SSOAuth</servlet-name><servlet-class>SSO.SSOAuth</servlet-class><!-- <init-param> <param-name>domainname</param-name> <param-value>localhost</param-value> </init-param> <init-param> <param-name>cookiename</param-name> <param-value>SsoTicket</param-value> </init-param> --></servlet><servlet-mapping><servlet-name>SSOAuth</servlet-name><url-pattern>/SSOAuth</url-pattern></servlet-mapping><session-config><session-timeout> 30 </session-timeout></session-config><welcome-file-list><welcome-file> index.jsp </welcome-file></welcome-file-list></web-app>
6.2 SSOAuth類詳解
SSOAuth初始一個線程同步對象 ConcurrentMap SSOIDs; 此對象存儲key: cookie, value: 用戶信息
當用戶登陸時,判斷用戶的用戶名與密碼是否正確,若是正確, 使用相應算法生成cookie值 和 用戶信息保存在SSOIDs中;
登陸成功後,將cookie信息讓瀏覽器進行保存;
SSOWebDemo1中發出SSO校驗請求,根據傳遞的cookie參數值判斷是否有效,若是有效,將對應的用戶信息返回;
SSOWebDemo1中發出退出操做請求,根據cookie參數來刪除SSOIDs對應的用戶信息
6.3 登陸頁面
<%@page contentType="text/html"%><%@page pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>登陸</title><link rel="STYLESHEET" type="text/css" href="css.css"></head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0"><br><div><table border="0" cellpadding="0" cellspacing="0" width="748"><tr><td height="85" width="300"><h3>統一登陸頁面</h3><br><h1>登陸頁面</h1></td></tr></table><table border="0" cellpadding="0" cellspacing="0" width="748"><tr><td><table border='0' cellspacing='0' cellpadding='0'><tr><td width='598' valign='top'><p><form action='/SSOAuth/SSOAuth' method='post'>用戶名: <input type='text' name='username'><br><br>密 碼: <input type='password' name='password'> <br><input type='hidden' name='goto' value=<%=request.getParameter("goto")%>></input><br><input type='submit' value='登陸' style="width:100px;"></input></form></p></td></tr></table></td></tr></tr></table></div></body></html>