本文實現了Web-SSO單點登錄功能,實例中包含三個工程, SSOAuth, SSOWebDemo1, SSOWebDemo2, SSOAuth爲認證系統,使用SSOWebDemo1登錄系統時,要使用SSOAuth進行鑑權, 登錄上Demo1後,系統可實現自動登錄到Demo2的功能
本文開發工具:
MyEclipse10: https://pan.baidu.com/s/1eRPzTJG
JDK1.7 : https://pan.baidu.com/s/1jHWJSdK
相關demo下載:http://www.wisdomdd.cn/Wisdom/resource/articleDetail.htm?resourceId=490
1.認識SSO
單點登錄(Single Sign On),簡稱爲 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。
單點登錄的機制其實是比較簡單的,用一個現實中的例子做比較。頤和園是北京著名的旅遊景點,也是我常去的地方。在頤和園內部有許多獨立的景點,例如“蘇州 街”、“佛香閣”和“德和園”,都可以在各個景點門口單獨買票。很多遊客需要遊玩所有的景點,這種買票方式很不方便,需要在每個景點門口排隊買票,錢包拿 進拿出的,容易丟失,很不安全。於是絕大多數遊客選擇在大門口買一張通票(也叫套票),就可以玩遍所有的景點而不需要重新再買票。他們只需要在每個景點門
口出示一下剛纔買的套票就能夠被允許進入每個獨立的景點。
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++) {
-
//根據名稱判斷是否含有需要處理的cookie
-
if (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>\n<head>\n<title>Error</title>\n</head>\n<body>\n");
-
-
pw.print("<h1>The resource did not process correctly</h1>\n<pre>\n");
-
pw.print(stackTrace);
-
pw.print("</pre></body>\n</html>");
-
pw.close();
-
ps.close();
-
response.getOutputStream().close();
-
} catch (Exception ex) {
-
}
-
} else
-
try {
-
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自身的登錄退出操作
-
<?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對應的用戶信息
-
package SSO;
-
-
import java.io.IOException;
-
import java.io.PrintStream;
-
import java.io.PrintWriter;
-
import java.util.Date;
-
import java.util.concurrent.ConcurrentHashMap;
-
import java.util.concurrent.ConcurrentMap;
-
import javax.servlet.RequestDispatcher;
-
import javax.servlet.ServletConfig;
-
import javax.servlet.ServletContext;
-
import javax.servlet.ServletException;
-
import javax.servlet.http.Cookie;
-
import javax.servlet.http.HttpServlet;
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
-
import com.alibaba.fastjson.JSON;
-
-
public class SSOAuth extends HttpServlet {
-
private static ConcurrentMap SSOIDs;
-
protected void processRequest(HttpServletRequest request,
-
HttpServletResponse response) throws ServletException, IOException {
-
PrintWriter out = response.getWriter();
-
String action = request.getParameter("action");
-
String gotoURL = request.getParameter("goto");
-
String result = "failed";
-
if (action == null) {
-
userLogin(request, response);
-
} else if (action.equals("authcookie")) {
-
String myCookie = request.getParameter("cookiename");
-
if (myCookie != null)
-
result = authCookie(myCookie);
-
out.print(result);
-
out.close();
-
} else if (action.equals("logout")) {
-
String myCookie = request.getParameter("cookiename");
-
logout(myCookie);
-
out.close();
-
}
-
}
-
-
public static String authCookie(String value) {
-
User user= (User) SSOIDs.get(value);
-
String strUserInfo = "";
-
if (user == null) {
-
strUserInfo = "failed";
-
System.out.println("Authentication failed!");
-
} else {
-
strUserInfo = JSON.toJSONString(user);
-
System.out.println("Authentication success!");
-
}
-
return strUserInfo;
-
}
-
-
/**
-
* 校驗用戶登錄信息是否正確
-
* @param userName
-
* @param password
-
* @return
-
*/
-
public static User authUser(String userName, String password) {
-
if(null == userName || null == password){
-
return null;
-
}
-
-
User loginUser = null;
-
for(User user : Constant.UserAccounts){
-
if(userName.equals(user.getUserName()) && password.equals(user.getPassword())){
-
loginUser = user;
-
}
-
}
-
-
return loginUser;
-
}
-
-
-
-
protected void doGet(HttpServletRequest request,
-
HttpServletResponse response) throws ServletException, IOException {
-
processRequest(request, response);
-
}
-
-
protected void doPost(HttpServletRequest request,
-
HttpServletResponse response) throws ServletException, IOException {
-
processRequest(request, response);
-
}
-
-
public String getServletInfo() {
-
return "Short description";
-
}
-
-
public void init(ServletConfig config) throws ServletException {
-
super.init(config);
-
/*this.domainname = config.getInitParameter("domainname");
-
this.cookiename = config.getInitParameter("cookiename");*/
-
SSOIDs = new ConcurrentHashMap();
-
}
-
-
private static String createUID(String userName) {
-
Date now = new Date();
-
long time = now.getTime();
-
return userName + time;
-
}
-
-
/**
-
*
-
* @param UID
-
*/
-
private void logout(String UID) {
-
System.out.println("Logout for " + UID);
-
SSOIDs.remove(UID);
-
}
-
-
/**
-
*
-
* @param request
-
* @param response
-
* @throws ServletException
-
* @throws IOException
-
*/
-
private void userLogin(HttpServletRequest request,
-
HttpServletResponse response) throws ServletException, IOException {
-
String userName = request.getParameter("username");
-
String password = request.getParameter("password");
-
-
-
User user = authUser(userName, password);
-
if (null == user){
-
getServletContext().getRequestDispatcher("/failed.html").forward(request, response);
-
}else{
-
String gotoURL = request.getParameter("goto");
-
String newID = createUID(userName);
-
SSOIDs.put(newID, user);
-
add2Cookie(response, Constant.CookieName, newID, 60 * 1000);
-
System.out.println("login success, goto back url:" + gotoURL);
-
if (gotoURL != null) {
-
PrintWriter out = response.getWriter();
-
response.sendRedirect(gotoURL);
-
out.close();
-
}
-
}
-
}
-
-
/**
-
*
-
* @param response
-
* @param cookieName
-
* @param cookieValue
-
* @param maxAge
-
*/
-
private void add2Cookie(HttpServletResponse response, String cookieName,
-
String cookieValue, int maxAge) {
-
Cookie cookie = new Cookie(cookieName, cookieValue);
-
cookie.setDomain(Constant.DomainName);
-
cookie.setPath("/");
-
cookie.setMaxAge(maxAge);
-
response.addCookie(cookie);
-
}
-
}
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>