SSO英文全稱Single Sign On,單點登陸。SSO是在多個應用系統中,用戶只須要登陸一次就能夠訪問全部相互信任的應用系統。它包括能夠將此次主要的登陸映射到其餘應用中用於同一個用戶的登陸的機制。它是目前比較流行的企業業務整合的解決方案之一。javascript
當用戶第一次訪問應用系統1的時候,由於尚未登陸,會被引導到認證系統中進行登陸;根據用戶提供的登陸信息,認證系統進行身份校驗,若是經過校驗,應該返回給用戶一個認證的憑據--token;用戶再訪問別的應用的時候就會將這個token帶上,做爲本身認證的憑據,應用系統接受到請求以後會把token送到認證系統進行校驗,檢查token的合法性。若是經過校驗,用戶就能夠在不用再次登陸的狀況下訪問應用系統2和應用系統3了 。html
token的意思是「令牌」,是服務端生成的一串字符串,做爲客戶端進行請求的一個標識。java
當用戶第一次登陸後,服務器生成一個token並將此token返回給客戶端,客戶端收到token後把它存儲起來,能夠放在cookie或者Local Storage(本地存儲)裏。 之後客戶端只需帶上這個token前來請求數據便可,無需再次帶上用戶名和密碼。web
簡單token的組成;uid(用戶惟一的身份標識)、time(當前時間的時間戳)、sign(簽名,token的前幾位以哈希算法壓縮成的必定長度的十六進制字符串。爲防止token泄露)。算法
實際上,HTTP協議是無狀態的,單個系統的會話由服務端Session進行維持,Session保持會話的原理是經過Cookie把sessionId寫入瀏覽器,每次訪問都會自動攜帶所有Cookie,在服務端讀取其中的sessionId進行驗證明現會話保持。同域下單點登陸其實就是手寫token代替sessionId進行會話認證。跨域
token的生成瀏覽器
服務端生成token後,將token與user對象存儲在Map結構中,token爲Key,user對象爲value,response.addCookie()生成新的Cookie,名爲token,值爲token的值。tomcat
token過時移除服務器
將服務端的token從Map中移除,再刪除瀏覽器端的名爲token的Cookie。cookie
本例演示的是同域SSO
test-sso-auth的訪問地址是:http://check.x.com:8080/test-sso-auth
test-sso1的訪問地址是:http://demo1.x.com:8080/test-sso1
test-sso2的訪問地址是:http://demo2.x.com:8080/test-sso2
爲了測試方便,須要在hosts文件中,修改IP與域名的映射關係,以下:
1 127.0.0.1 demo1.x.com 2 127.0.0.1 demo2.x.com 3 127.0.0.1 check.x.com
一、新建一個Maven Web項目做爲認證中心,test-sso-auth,參考:【Maven】Eclipse 使用Maven建立Java Web項目
二、新建一個登陸Servlet,處理登陸請求,LoginServlet.java
1 package com.test.sso.servlet; 2 3 import java.io.IOException; 4 import javax.servlet.ServletException; 5 import javax.servlet.http.Cookie; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import com.test.sso.util.SSOUtil; 11 12 /** 13 * Servlet implementation class LoginServlet 14 */ 15 public class LoginServlet extends HttpServlet { 16 private static final long serialVersionUID = 1L; 17 18 /** 19 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 20 */ 21 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 22 // TODO Auto-generated method stub 23 String username = request.getParameter("username"); 24 String password = request.getParameter("password"); 25 String redirectUrl = request.getParameter("redirectUrl"); 26 if(SSOUtil.doLogin(username, password)) { 27 Cookie cookie = new Cookie("sso", "123456"); 28 // 設置到父域中,全部子域能夠使用cookie 29 cookie.setDomain("x.com"); 30 // cookie設置的域的最頂層 31 cookie.setPath("/"); 32 response.addCookie(cookie); 33 response.sendRedirect(redirectUrl); 34 }else { 35 36 //這句話的意思,是讓瀏覽器用utf8來解析返回的數據 37 response.setHeader("Content-type", "text/html;charset=UTF-8"); 38 //這句話的意思,是告訴servlet用UTF-8轉碼,而不是用默認的ISO8859 39 response.setCharacterEncoding("UTF-8"); 40 response.getWriter().print("登陸失敗"); 41 } 42 } 43 44 /** 45 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 46 */ 47 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 48 // TODO Auto-generated method stub 49 doGet(request, response); 50 } 51 52 }
三、新建一個驗證Token的Servlet,驗證Token的有效性,CheckServlet.java
1 package com.test.sso.servlet; 2 3 import java.io.IOException; 4 import javax.servlet.ServletException; 5 import javax.servlet.http.Cookie; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import com.test.sso.util.SSOUtil; 11 12 /** 13 * Servlet implementation class CheckServlet 14 */ 15 public class CheckServlet extends HttpServlet { 16 private static final long serialVersionUID = 1L; 17 18 /** 19 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 20 */ 21 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 22 23 String username = request.getParameter("username"); 24 String password = request.getParameter("password"); 25 boolean checkLogin = SSOUtil.checkLogin(username, password); 26 27 //這句話的意思,是讓瀏覽器用utf8來解析返回的數據 28 response.setHeader("Content-type", "text/html;charset=UTF-8"); 29 //這句話的意思,是告訴servlet用UTF-8轉碼,而不是用默認的ISO8859 30 response.setCharacterEncoding("UTF-8"); 31 response.getWriter().print(checkLogin); 32 33 } 34 35 /** 36 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 37 */ 38 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 39 // TODO Auto-generated method stub 40 doGet(request, response); 41 } 42 43 }
四、編輯一個Util,處理登陸,驗證token的邏輯,SSOUtil.java
1 package com.test.sso.util; 2 3 import javax.servlet.http.Cookie; 4 import javax.servlet.http.HttpServletRequest; 5 6 public class SSOUtil { 7 8 public static String USERNAME = "admin"; 9 public static String PASSWORD = "123456"; 10 11 public static boolean doLogin(String username, String password) { 12 if (USERNAME.equals(username) && PASSWORD.equals(password)) { 13 return true; 14 } 15 return false; 16 } 17 18 public static boolean checkLogin(String username, String password) { 19 20 if ("sso".equals(username) && "123456".equals(password)) { 21 return true; 22 } 23 return false; 24 } 25 }
五、編輯web.xml文件,註冊相應的servlet
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 5 id="WebApp_ID" version="3.0"> 6 <display-name>/test-sso-auth</display-name> 7 <servlet> 8 <servlet-name>LoginServlet</servlet-name> 9 <display-name>LoginServlet</display-name> 10 <description></description> 11 <servlet-class>com.test.sso.servlet.LoginServlet</servlet-class> 12 </servlet> 13 <servlet> 14 <servlet-name>CheckServlet</servlet-name> 15 <display-name>CheckServlet</display-name> 16 <description></description> 17 <servlet-class>com.test.sso.servlet.CheckServlet</servlet-class> 18 </servlet> 19 20 <servlet-mapping> 21 <servlet-name>LoginServlet</servlet-name> 22 <url-pattern>/login</url-pattern> 23 </servlet-mapping> 24 <servlet-mapping> 25 <servlet-name>CheckServlet</servlet-name> 26 <url-pattern>/check</url-pattern> 27 </servlet-mapping> 28 <welcome-file-list> 29 <welcome-file>index.jsp</welcome-file> 30 </welcome-file-list> 31 </web-app>
六、編輯統一登陸界面,login.jsp
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <html> 4 <body> 5 <h2>統一登陸界面</h2> 6 <form action="login" method="POST"> 7 <span>用戶名:</span><input type="text" name="username" /> 8 <span>密碼:</span><input type="password" name="password" /> 9 <input type="hidden" value="${param.redirectUrl }" name="redirectUrl"/> 10 <input type="submit"/> 11 </form> 12 </body> 13 </html>
一、新建一個Maven Web項目做爲子系統,test-sso1,參考:【Maven】Eclipse 使用Maven建立Java Web項目
二、新建一個Servlet,邏輯爲,當用戶請求時,驗證用戶是否登陸(能夠用攔截器作),
已登陸狀況,直接返回用戶請求的界面
未登陸狀況,跳轉到統一登陸界面進行登陸
1 package com.test.sso.servlet; 2 3 import java.io.IOException; 4 import java.net.URLEncoder; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.Cookie; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 12 import com.test.sso.util.HttpConnectionUtils; 13 14 /** 15 * Servlet implementation class HomeServlet 16 */ 17 public class Home1Servlet extends HttpServlet { 18 private static final long serialVersionUID = 1L; 19 20 /** 21 * Default constructor. 22 */ 23 public Home1Servlet() { 24 // TODO Auto-generated constructor stub 25 } 26 27 /** 28 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 29 */ 30 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 31 // TODO Auto-generated method stub 32 // response.getWriter().append("Served at: ").append(request.getContextPath()); 33 // String username = request.getParameter("username"); 34 // String password = request.getParameter("password"); 35 boolean checkLogin = checkLogin(request); 36 if(checkLogin) { 37 // 登陸-跳轉到主頁 38 request.getRequestDispatcher("home1.jsp").forward(request, response); 39 }else { 40 // 未登陸-跳轉的登陸界面 41 String redirectUrl = "http://demo1.x.com:8080/test-sso1/home1"; 42 43 // request.getSession().setAttribute("redirectUrl", redirectUrl); 44 // request.getRequestDispatcher("login.jsp").forward(request, response); 45 46 response.sendRedirect("http://check.x.com:8080/test-sso-auth/login.jsp?redirectUrl=" + URLEncoder.encode(redirectUrl, "UTF-8")); 47 } 48 } 49 50 private boolean checkLogin(HttpServletRequest request) { 51 Cookie[] cookies = request.getCookies(); 52 if(cookies != null) { 53 for (Cookie cookie : cookies) { 54 55 String method = HttpConnectionUtils.getMethod("http://check.x.com:8080/test-sso-auth/check?" + "username=" +cookie.getName() + "&password=" + cookie.getValue()); 56 if("true".equals(method)) { 57 return true; 58 } 59 } 60 } 61 return false; 62 } 63 64 /** 65 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 66 */ 67 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 68 // TODO Auto-generated method stub 69 doGet(request, response); 70 } 71 72 }
其中代碼中使用的http工具類HttpConnectionUtils,參考【JAVA】經過URLConnection/HttpURLConnection發送HTTP請求的方法(一)
三、編輯web.xml文件,註冊servlet
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 5 id="WebApp_ID" version="3.0"> 6 <display-name>/test-sso1</display-name> 7 <servlet> 8 <servlet-name>Home1Servlet</servlet-name> 9 <display-name>Home1Servlet</display-name> 10 <description></description> 11 <servlet-class>com.test.sso.servlet.Home1Servlet</servlet-class> 12 </servlet> 13 14 <servlet-mapping> 15 <servlet-name>Home1Servlet</servlet-name> 16 <url-pattern>/home1</url-pattern> 17 </servlet-mapping> 18 <welcome-file-list> 19 <welcome-file>index.jsp</welcome-file> 20 </welcome-file-list> 21 </web-app>
四、編輯用戶請求界面home1.jsp
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <!DOCTYPE html> 4 <html> 5 <head> 6 <meta charset="UTF-8"> 7 <title>Insert title here</title> 8 </head> 9 <body> 10 <h2>Home 1 JSP</h2> 11 </body> 12 </html>
搭建方式,參考上面的test-sso1子系統
將test-sso-auth、test-sso1 和 test-sso2 三個項目發佈到tomcat中,並啓動tomcat
流程:
一、使用http://demo1.x.com:8080/test-sso1/home1,請求界面
二、未登陸,跳轉到統一登陸界面(http://check.x.com:8080/test-sso-auth/login.jsp),輸出用戶名/密碼:admin/123456
三、登陸完成,跳轉到http://demo1.x.com:8080/test-sso1/home1界面
四、打開http://demo2.x.com:8080/test-sso2/home2,查看是否完成登陸
本例演示的是跨域SSO
test-sso-auth的訪問地址是:http://check.x.com:8080/test-sso-auth
test-sso1的訪問地址是:http://demo1.a.com:8080/test-sso1
test-sso2的訪問地址是:http://demo2.b.com:8080/test-sso2
爲了測試方便,須要在hosts文件中,修改IP與域名的映射關係,以下:
1 127.0.0.1 check.x.com 2 127.0.0.1 demo1.a.com 3 127.0.0.1 demo2.b.com
爲了方便,本例在上面同域SSO案例的代碼上進行修改
修改了LoginServlet.java,登陸成功時,回調前沒有寫入Cookie,而是吧Cookie的值在url中傳回給子系統,讓子系統作邏輯處理
1 package com.test.sso.servlet; 2 3 import java.io.IOException; 4 import javax.servlet.ServletException; 5 import javax.servlet.http.Cookie; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import com.test.sso.util.SSOUtil; 11 12 /** 13 * Servlet implementation class LoginServlet 14 */ 15 public class LoginServlet extends HttpServlet { 16 private static final long serialVersionUID = 1L; 17 18 /** 19 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 20 */ 21 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 22 // TODO Auto-generated method stub 23 String username = request.getParameter("username"); 24 String password = request.getParameter("password"); 25 String redirectUrl = request.getParameter("redirectUrl"); 26 if(SSOUtil.doLogin(username, password)) { 27 // Cookie cookie = new Cookie("sso", "123456"); 28 // // 設置到父域中,全部子域能夠使用cookie 29 // cookie.setDomain("x.com"); 30 // // cookie設置的域的最頂層 31 // cookie.setPath("/"); 32 // response.addCookie(cookie); 33 // response.sendRedirect(redirectUrl); 34 35 response.sendRedirect(redirectUrl + "?cname=sso&cval=123456"); 36 }else { 37 38 //這句話的意思,是讓瀏覽器用utf8來解析返回的數據 39 response.setHeader("Content-type", "text/html;charset=UTF-8"); 40 //這句話的意思,是告訴servlet用UTF-8轉碼,而不是用默認的ISO8859 41 response.setCharacterEncoding("UTF-8"); 42 response.getWriter().print("登陸失敗"); 43 } 44 } 45 46 /** 47 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 48 */ 49 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 50 // TODO Auto-generated method stub 51 doGet(request, response); 52 } 53 54 }
一、修改業務的Servlet,Home1Servlet.java
修改內容:a、接收認證中心在url傳過來的Cookie的值,生成Cookie並保持,且傳到界面
b、界面上,利用HTML的script標籤跨域寫Cookie,增長子系統b的Cookie
1 package com.test.sso.servlet; 2 3 import java.io.IOException; 4 import java.net.URLEncoder; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.Cookie; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 12 import com.test.sso.util.HttpConnectionUtils; 13 14 /** 15 * Servlet implementation class HomeServlet 16 */ 17 public class Home1Servlet extends HttpServlet { 18 private static final long serialVersionUID = 1L; 19 20 /** 21 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 22 */ 23 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 24 25 // 認證中心回調,驗證 26 boolean checkSSO = checkSSO(request, response); 27 boolean checkLogin = checkLogin(request); 28 29 if(checkLogin || checkSSO) { 30 // 登陸-跳轉到主頁 31 request.getRequestDispatcher("home1.jsp").forward(request, response); 32 }else { 33 // 未登陸-跳轉的登陸界面 34 String redirectUrl = "http://demo1.a.com:8080/test-sso1/home1"; 35 36 // request.getSession().setAttribute("redirectUrl", redirectUrl); 37 // request.getRequestDispatcher("login.jsp").forward(request, response); 38 response.sendRedirect("http://check.x.com:8080/test-sso-auth/login.jsp?redirectUrl=" + URLEncoder.encode(redirectUrl, "UTF-8")); 39 } 40 } 41 42 private boolean checkLogin(HttpServletRequest request) { 43 Cookie[] cookies = request.getCookies(); 44 if(cookies != null) { 45 for (Cookie cookie : cookies) { 46 47 String method = HttpConnectionUtils.getMethod("http://check.x.com:8080/test-sso-auth/check?" + "username=" +cookie.getName() + "&password=" + cookie.getValue()); 48 if("true".equals(method)) { 49 request.getSession().setAttribute("canme", cookie.getName()); 50 request.getSession().setAttribute("cval", cookie.getValue()); 51 return true; 52 } 53 } 54 } 55 return false; 56 } 57 58 private boolean checkSSO(HttpServletRequest request, HttpServletResponse response) { 59 String cname = request.getParameter("cname"); 60 String cval = request.getParameter("cval"); 61 if(cname != null && cname.length() > 0 && cval != null && cval.length() > 0) { 62 // 驗證 63 String method = HttpConnectionUtils.getMethod("http://check.x.com:8080/test-sso-auth/check?" + "username=" +cname + "&password=" + cval); 64 if("true".equals(method)) { 65 request.getSession().setAttribute("canme", cname); 66 request.getSession().setAttribute("cval", cval); 67 68 Cookie cookie = new Cookie(cname, cval); 69 // cookie設置的域的最頂層 70 cookie.setPath("/"); 71 response.addCookie(cookie); 72 return true; 73 } 74 } 75 return false; 76 } 77 78 /** 79 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 80 */ 81 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 82 // TODO Auto-generated method stub 83 doGet(request, response); 84 } 85 86 }
二、界面上,利用HTML的script標籤跨域寫Cookie,增長子系統b的Cookie,服務端增長對應的Servlet服務,AddCookieServlet.java
1 package com.test.sso.servlet; 2 3 import java.io.IOException; 4 import javax.servlet.ServletException; 5 import javax.servlet.http.Cookie; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 /** 11 * Servlet implementation class AddTokenServlet 12 */ 13 public class AddCookieServlet extends HttpServlet { 14 private static final long serialVersionUID = 1L; 15 16 /** 17 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 18 */ 19 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 20 String cname = request.getParameter("cname"); 21 String cval = request.getParameter("cval"); 22 Cookie cookie = new Cookie(cname, cval); 23 // cookie設置的域的最頂層 24 cookie.setPath("/"); 25 response.addCookie(cookie); 26 } 27 28 /** 29 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 30 */ 31 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 32 // TODO Auto-generated method stub 33 doGet(request, response); 34 } 35 36 }
三、修改home1.jsp界面
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <!DOCTYPE html> 4 <html> 5 <head> 6 <meta charset="UTF-8"> 7 <title>Insert title here</title> 8 </head> 9 <body> 10 <h2>Home 1 JSP</h2> 11 </body> 12 13 <script type="text/javascript" src="http://demo2.b.com/addCookie?cname=${params.cname }&cval=${params.cval }"></script> 14 15 </html>
四、在web.xml中註冊新增的Servlet
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 5 id="WebApp_ID" version="3.0"> 6 <display-name>/test-sso1</display-name> 7 <servlet> 8 <servlet-name>Home1Servlet</servlet-name> 9 <display-name>Home1Servlet</display-name> 10 <description></description> 11 <servlet-class>com.test.sso.servlet.Home1Servlet</servlet-class> 12 </servlet> 13 <servlet> 14 <description> 15 </description> 16 <display-name>AddCookieServlet</display-name> 17 <servlet-name>AddCookieServlet</servlet-name> 18 <servlet-class>com.test.sso.servlet.AddCookieServlet</servlet-class> 19 </servlet> 20 21 <servlet-mapping> 22 <servlet-name>Home1Servlet</servlet-name> 23 <url-pattern>/home1</url-pattern> 24 </servlet-mapping> 25 <servlet-mapping> 26 <servlet-name>AddCookieServlet</servlet-name> 27 <url-pattern>/addCookie</url-pattern> 28 </servlet-mapping> 29 <welcome-file-list> 30 <welcome-file>index.jsp</welcome-file> 31 </welcome-file-list> 32 </web-app>
搭建方式,參考上面的test-sso1子系統
將test-sso-auth、test-sso1 和 test-sso2 三個項目發佈到tomcat中,並啓動tomcat
流程:
一、使用http://demo1.a.com:8080/test-sso1/home1,請求界面
二、未登陸,跳轉到統一登陸界面(http://check.x.com:8080/test-sso-auth/login.jsp),輸出用戶名/密碼:admin/123456
三、登陸完成,跳轉到http://demo1.a.com:8080/test-sso1/home1界面
四、打開http://demo2.b.com:8080/test-sso2/home2,查看是否完成登陸