1、單點登錄,就是多系統,單一位置登錄,實現多系統同時登錄的一種技術。單點登錄一般是用於互相授信的系統,實現單一位置登錄,全系統有效的。
注意:區分與三方登錄(第三方登錄) ,三方登錄:某系統,使用其他系統的用戶,實現本系統登錄的方式。如,在王者榮耀中使用微信或者QQ登錄。解決信息孤島和用戶不對等的實現方案。
2、單點登錄方案選擇:
2.1、方案一、Session跨域(熟悉即可)。
1 所謂Session跨域就是摒棄了系統(Tomcat)提供的Session,而使用自定義的類似Session的機制來保存客戶端數據的一種解決方案。 2 如:通過設置cookie的domain來實現cookie的跨域傳遞。在cookie中傳遞一個自定義的session_id。這個session_id是客戶端的唯一標記。將這個標記作為key,將客戶端需要保存的數據作為value,在服務端進行保存(數據庫保存或NoSQL保存)。這種機制就是Session的跨域解決。 3 什么跨域: 客戶端請求的時候,請求的服務器,不是同一個IP,端口,域名,主機名的時候,都稱為跨域。 4 什么是域:在應用模型,一個完整的,有獨立訪問路徑的功能集合稱為一個域。如:百度稱為一個應用或系統。百度下有若干的域,如:搜索引擎(www.baidu.com),百度貼吧(tie.baidu.com),百度知道(zhidao.baidu.com),百度地圖(map.baidu.com)等。域信息,有時也稱為多級域名。域的划分: 以IP,端口,域名,主機名為標准,實現划分。 5 localhost / 127.0.0.1 6 7 使用cookie跨域共享,是session跨域的一種解決方案。 8 jsessionid是和servlet綁定的http session的唯一標記。 9 10 cookie應用 - new Cookie("", ""). 11 request.getCookies() -> cookie[] -> 迭代找到需要使用的cookie 12 response.addCookie(). 13 cookie.setDomain() - 為cookie設定有效域范圍。 14 cookie.setPath() - 為cookie設定有效URI范圍。
代碼實現如下所示:
1 package com.bie.controller; 2 3 import java.util.UUID; 4 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 8 import org.springframework.stereotype.Controller; 9 import org.springframework.web.bind.annotation.RequestMapping; 10 11 import com.bie.utils.CookieUtils; 12 13 /** 14 * 15 * @author biehl 16 * 17 * 1、單點登錄實現方案一,Session跨域實現 18 */ 19 @Controller 20 public class SsoController { 21 22 /** 23 * 請求控制層的方法 24 * 25 * @param request 26 * @param response 27 * @return 28 */ 29 @RequestMapping("/sso") 30 public String test(HttpServletRequest request, HttpServletResponse response) { 31 // JSESSIONID代表了系統中http的唯一標記 32 33 // custom_global_session_id。全局session的id 34 String cookieName = "custom_global_session_id"; 35 String encodeString = "UTF-8"; 36 37 // 獲取到cookie的value值 38 String cookieValue = CookieUtils.getCookieValue(request, cookieName, encodeString); 39 40 // 判斷cookie的value值是否為空 41 if (null == cookieValue || "".equals(cookieValue.trim())) { 42 System.out.println("無cookie,生成新的cookie數據"); 43 // 生成一個cookie的value值 44 cookieValue = UUID.randomUUID().toString(); 45 } 46 47 // 根據cookieValue訪問數據存儲,獲取客戶端數據。 48 // 將生產的cookie的value值設置到cookie中 49 // 參數0代表關閉瀏覽器自動刪除cookie. 50 CookieUtils.setCookie(request, response, cookieName, cookieValue, 0, encodeString); 51 52 return "/ok.jsp"; 53 } 54 55 }
解析域名和設置cookie的方法工具類:
1 package com.bie.utils; 2 3 import java.io.UnsupportedEncodingException; 4 import java.net.URLDecoder; 5 import java.net.URLEncoder; 6 7 import javax.servlet.http.Cookie; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 /** 12 * 13 * Cookie 工具類 14 * 15 */ 16 public final class CookieUtils { 17 18 /** 19 * 得到Cookie的值, 不編碼 20 * 21 * @param request 22 * @param cookieName 23 * @return 24 */ 25 public static String getCookieValue(HttpServletRequest request, String cookieName) { 26 return getCookieValue(request, cookieName, false); 27 } 28 29 /** 30 * 得到Cookie的值, 31 * 32 * @param request 33 * @param cookieName 34 * @return 35 */ 36 public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) { 37 Cookie[] cookieList = request.getCookies(); 38 if (cookieList == null || cookieName == null) { 39 return null; 40 } 41 String retValue = null; 42 try { 43 for (int i = 0; i < cookieList.length; i++) { 44 if (cookieList[i].getName().equals(cookieName)) { 45 if (isDecoder) { 46 retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8"); 47 } else { 48 retValue = cookieList[i].getValue(); 49 } 50 break; 51 } 52 } 53 } catch (UnsupportedEncodingException e) { 54 e.printStackTrace(); 55 } 56 return retValue; 57 } 58 59 /** 60 * 得到Cookie的值, 61 * 62 * @param request 63 * @param cookieName 64 * @return 65 */ 66 public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) { 67 Cookie[] cookieList = request.getCookies(); 68 if (cookieList == null || cookieName == null) { 69 return null; 70 } 71 String retValue = null; 72 try { 73 for (int i = 0; i < cookieList.length; i++) { 74 if (cookieList[i].getName().equals(cookieName)) { 75 retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString); 76 break; 77 } 78 } 79 } catch (UnsupportedEncodingException e) { 80 e.printStackTrace(); 81 } 82 return retValue; 83 } 84 85 /** 86 * 設置Cookie的值 不設置生效時間默認瀏覽器關閉即失效,也不編碼 87 */ 88 public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, 89 String cookieValue) { 90 setCookie(request, response, cookieName, cookieValue, -1); 91 } 92 93 /** 94 * 設置Cookie的值 在指定時間內生效,但不編碼 95 */ 96 public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, 97 String cookieValue, int cookieMaxage) { 98 setCookie(request, response, cookieName, cookieValue, cookieMaxage, false); 99 } 100 101 /** 102 * 設置Cookie的值 不設置生效時間,但編碼 103 */ 104 public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, 105 String cookieValue, boolean isEncode) { 106 setCookie(request, response, cookieName, cookieValue, -1, isEncode); 107 } 108 109 /** 110 * 設置Cookie的值 在指定時間內生效, 編碼參數 111 */ 112 public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, 113 String cookieValue, int cookieMaxage, boolean isEncode) { 114 doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode); 115 } 116 117 /** 118 * 設置Cookie的值 在指定時間內生效, 編碼參數(指定編碼) 119 */ 120 public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, 121 String cookieValue, int cookieMaxage, String encodeString) { 122 doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString); 123 } 124 125 /** 126 * 刪除Cookie帶cookie域名 127 */ 128 public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) { 129 doSetCookie(request, response, cookieName, "", -1, false); 130 } 131 132 /** 133 * 設置Cookie的值,並使其在指定時間內生效 134 * 135 * @param request 136 * 請求,請求對象,分析域信息 137 * @param response 138 * 響應 139 * @param cookieName 140 * cookie的名稱 141 * @param cookieValue 142 * cookie的值 143 * @param cookieMaxage 144 * cookie生效的最大秒數。不做設定,關閉瀏覽器就無效了 145 * @param isEncode 146 * 是否需要編碼 147 */ 148 private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, 149 String cookieValue, int cookieMaxage, boolean isEncode) { 150 try { 151 // 判斷cookie的value值是否等於null 152 if (cookieValue == null) { 153 cookieValue = ""; 154 } else if (isEncode) { 155 // 判斷是否需要utf8編碼 156 cookieValue = URLEncoder.encode(cookieValue, "utf-8"); 157 } 158 // 創建cookie,最好做非空判斷的 159 Cookie cookie = new Cookie(cookieName, cookieValue); 160 if (cookieMaxage > 0) { 161 // 如果cookie生效的最大秒數大於0,就設置這個值 162 cookie.setMaxAge(cookieMaxage); 163 } 164 if (null != request) { 165 // 分析解析域名 166 String domainName = getDomainName(request); 167 // 如果不等於localhost這個值,就設置一個domainName 168 if (!"localhost".equals(domainName)) { 169 // 設置域名的cookie 170 cookie.setDomain(domainName); 171 } 172 } 173 // 從根路徑下的后面任意路徑地址,cookie都有效 174 cookie.setPath("/"); 175 // response響應寫入到客戶端即可 176 response.addCookie(cookie); 177 } catch (Exception e) { 178 e.printStackTrace(); 179 } 180 } 181 182 /** 183 * 設置Cookie的值,並使其在指定時間內生效 184 * 185 * @param request 186 * @param response 187 * @param cookieName 188 * @param cookieValue 189 * @param cookieMaxage 190 * cookie生效的最大秒數 191 * @param encodeString 192 */ 193 private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, 194 String cookieValue, int cookieMaxage, String encodeString) { 195 try { 196 if (cookieValue == null) { 197 cookieValue = ""; 198 } else { 199 cookieValue = URLEncoder.encode(cookieValue, encodeString); 200 } 201 Cookie cookie = new Cookie(cookieName, cookieValue); 202 if (cookieMaxage > 0) 203 cookie.setMaxAge(cookieMaxage); 204 if (null != request) { 205 // 根據獲取到的request請求,設置域名的cookie 206 String domainName = getDomainName(request); 207 if (!"localhost".equals(domainName)) { 208 // 設定一個域名。cookie就可以實現跨域訪問了。 209 cookie.setDomain(domainName); 210 } 211 } 212 cookie.setPath("/"); 213 response.addCookie(cookie); 214 } catch (Exception e) { 215 e.printStackTrace(); 216 } 217 } 218 219 /** 220 * 得到cookie的域名 221 * 222 * @param request 223 * 請求對象,包含了請求的信息 224 * @return 225 */ 226 private static final String getDomainName(HttpServletRequest request) { 227 // 定義一個變量domainName 228 String domainName = null; 229 230 // 獲取完整的請求URL地址。請求url,轉換為字符串類型 231 String serverName = request.getRequestURL().toString(); 232 // 判斷如果請求url地址為空或者為null 233 if (serverName == null || serverName.equals("")) { 234 domainName = ""; 235 } else { 236 // 不為空或者不為null,將域名轉換為小寫。域名不敏感的。大小寫一樣 237 serverName = serverName.toLowerCase(); 238 // 判斷開始如果以http://開頭的 239 if (serverName.startsWith("http://")) { 240 // 截取前七位字符 241 serverName = serverName.substring(7); 242 } else if (serverName.startsWith("https://")) { 243 // 否則如果開始以https://開始的。//截取前八位字符 244 serverName = serverName.substring(8); 245 } 246 // 找到/開始的位置,可以判斷end的值是否為-1,如果為-1的話說明沒有這個值 247 // 如果存在這個值,找到這個值的位置,否則返回值為-1 248 final int end = serverName.indexOf("/"); 249 // .test.com www.test.com.cn/sso.test.com.cn/.test.com.cn spring.io/xxxx/xxx 250 // 然后截取到0開始到/的位置 251 if (end != -1) { 252 // end等於-1。說明沒有/。否則end不等於-1說明有這個值 253 serverName = serverName.substring(0, end); 254 // 這是將\\.是轉義為.。然后進行分割操作。 255 final String[] domains = serverName.split("\\."); 256 // 獲取到長度 257 int len = domains.length; 258 // 注意,如果是兩截域名,一般沒有二級域名。比如spring.io/xxx/xxx,都是在spring.io后面/拼接的 259 // 如果是三截域名,都是以第一截為核心分割的。 260 // 如果是兩截的就保留。三截以及多截的就 261 // 多截進行匹配域名規則,第一個相當於寫*,然后加.拼接后面的域名地址 262 if (len > 3) { 263 // 如果是大於等於3截的,去掉第一個點之前的。留下剩下的域名 264 domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; 265 } else if (len <= 3 && len > 1) { 266 // 如果是2截和3截保留 267 domainName = "." + domains[len - 2] + "." + domains[len - 1]; 268 } else { 269 domainName = serverName; 270 } 271 } 272 } 273 // 如果域名不為空並且有這個: 274 if (domainName != null && domainName.indexOf(":") > 0) { 275 // 將:轉義。去掉端口port號,cookie(cookie的domainName)和端口無關。只看域名的。 276 String[] ary = domainName.split("\\:"); 277 domainName = ary[0]; 278 } 279 // 返回域名 280 System.out.println("==============================================" + domainName); 281 return domainName; 282 } 283 284 public static void main(String[] args) { 285 String serverName = "http://www.baidu.com/a"; 286 String domainName = null; 287 // 判斷如果請求url地址為空或者為null 288 if (serverName == null || serverName.equals("")) { 289 domainName = ""; 290 } else { 291 // 不為空或者不為null,將域名轉換為小寫。域名不敏感的。大小寫一樣 292 serverName = serverName.toLowerCase(); 293 // 判斷開始如果以http://開頭的 294 if (serverName.startsWith("http://")) { 295 // 截取前七位字符 296 serverName = serverName.substring(7); 297 } else if (serverName.startsWith("https://")) { 298 // 否則如果開始以https://開始的。//截取前八位字符 299 serverName = serverName.substring(8); 300 } 301 // 找到/開始的位置,可以判斷end的值是否為-1,如果為-1的話說明沒有這個值 302 final int end = serverName.indexOf("/"); 303 // .test.com www.test.com.cn/sso.test.com.cn/.test.com.cn spring.io/xxxx/xxx 304 // 然后截取到0開始到/的位置 305 serverName = serverName.substring(0, end); 306 final String[] domains = serverName.split("\\."); 307 int len = domains.length; 308 if (len > 3) { 309 domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; 310 } else if (len <= 3 && len > 1) { 311 domainName = "." + domains[len - 2] + "." + domains[len - 1]; 312 } else { 313 domainName = serverName; 314 } 315 } 316 317 if (domainName != null && domainName.indexOf(":") > 0) { 318 String[] ary = domainName.split("\\:"); 319 domainName = ary[0]; 320 } 321 } 322 323 }
效果實現如下所示:
C:\Windows\System32\drivers\etc\host配置文件配置一下地址映射。
# sso test
192.168.0.102 www.test.com
192.168.0.102 sso.test.com
192.168.0.102 cart.test.com
域名訪問,實現session跨域的效果:
2.2、Spring Session共享( 了解即可)。
1 spring-session技術是spring提供的用於處理集群會話共享的解決方案。spring-session技術是將用戶session數據保存到三方存儲容器中,如:mysql,redis等。 2 Spring-session技術是解決同域名下的多服務器集群session共享問題的。不能解決跨域session共享問題(如果要解決跨域sesion,就要搭建前端服務器nginx來解決這個問題)。 3 使用要求:
配置一個Spring提供的Filter,實現數據的攔截保存,並轉換為spring-session需要的會話對象。必須提供一個數據庫的表格信息(由spring-session提供,找spring-session-jdbc.jar/org/springframework/session/jdbc/*.sql,根據具體的數據庫找對應的SQL文件,做表格的創建)。 4 spring-session表:保存客戶端session對象的表格。 5 spring-session-attributes表:保存客戶端session中的attributes屬性數據的表格。 6 spring-session框架,是結合Servlet技術中的HTTPSession完成的會話共享機制。在代碼中是直接操作HttpSession對象的。
Spring Session共享、圖示如下所示:
基於Spring-session的代碼實現如下所示:
首先在配置文件中配置spring-session的filter攔截器。
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 5 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 6 id="WebApp_ID" version="2.5"> 7 8 <display-name>sso-cross-domain</display-name> 9 10 <welcome-file-list> 11 <welcome-file>index.html</welcome-file> 12 </welcome-file-list> 13 14 <!-- 重要:spring-session的filter攔截器 完成數據轉換的攔截器,spring提供的filter實現數據的攔截保存並轉換為spring-session所需的會話對象中。 --> 15 <filter> 16 <filter-name>springSessionRepositoryFilter</filter-name> 17 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 18 </filter> 19 <filter-mapping> 20 <filter-name>springSessionRepositoryFilter</filter-name> 21 <url-pattern>/*</url-pattern> 22 <dispatcher>REQUEST</dispatcher> 23 <dispatcher>ERROR</dispatcher> 24 </filter-mapping> 25 26 <!-- 字符集過濾器 --> 27 <filter> 28 <filter-name>charSetFilter</filter-name> 29 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 30 <init-param> 31 <param-name>encoding</param-name> 32 <param-value>UTF-8</param-value> 33 </init-param> 34 </filter> 35 <filter-mapping> 36 <filter-name>charSetFilter</filter-name> 37 <url-pattern>/*</url-pattern> 38 </filter-mapping> 39 40 <!-- 加載springmvc的配置文件 --> 41 <servlet> 42 <servlet-name>mvc</servlet-name> 43 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 44 <init-param> 45 <param-name>contextConfigLocation</param-name> 46 <param-value>classpath:applicationContext-mvc.xml</param-value> 47 </init-param> 48 <load-on-startup>1</load-on-startup> 49 </servlet> 50 <servlet-mapping> 51 <servlet-name>mvc</servlet-name> 52 <url-pattern>/</url-pattern> 53 </servlet-mapping> 54 55 56 </web-app>
然后配置提供數據庫的表格信息:
mysql文件路徑在這里,找到以后運行一下sql即可。/org/springframework/session/jdbc/schema-mysql.sql。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:mvc="http://www.springframework.org/schema/mvc" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xmlns:dwr="http://directwebremoting.org/schema/spring-dwr/spring-dwr-3.0.xsd" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans.xsd 9 http://www.springframework.org/schema/mvc 10 http://www.springframework.org/schema/mvc/spring-mvc.xsd 11 http://www.springframework.org/schema/context 12 http://www.springframework.org/schema/context/spring-context.xsd"> 13 14 <!-- 指定掃描的包 --> 15 <context:component-scan base-package="com.bie.controller" /> 16 17 <!-- 為SpringMVC配置注解驅動 --> 18 <mvc:annotation-driven /> 19 20 <!-- 為Spring基礎容器開啟注解配置信息 --> 21 <context:annotation-config /> 22 <!-- 就是用於提供HttpSession數據持久化操作的Bean對象。 23 對象定義后,可以實現數據庫相關操作配置,自動的實現HttpSession數據的持久化操作(CRUD) 24 --> 25 <bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration" /> 26 27 28 <bean id="dataSource" 29 class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 30 <property name="url" 31 value="jdbc:mysql://localhost:3306/springsession?useUnicode=true&characterEncoding=UTF8"></property> 32 <property name="username" value="root"></property> 33 <property name="password" value="123456"></property> 34 <property name="driverClassName" 35 value="com.mysql.jdbc.Driver"></property> 36 </bean> 37 38 <!-- 事務管理器。為JdbcHttpSessionConfiguration提供的事務管理器。 --> 39 <bean 40 class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 41 <constructor-arg ref="dataSource" /> 42 </bean> 43 44 </beans>
簡單的控制層代碼;
1 package com.bie.controller; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 6 import org.springframework.stereotype.Controller; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 9 /** 10 * 11 * @author biehl 12 * 13 * 1、SpringSession 14 * 15 * spring-session表:保存客戶端session對象的表格。 16 * spring-session-attributes表:保存客戶端session中的attributes屬性數據的表格。 17 * spring-session框架,是結合Servlet技術中的HTTPSession完成的會話共享機制。在代碼中是直接操作HttpSession對象的。 18 * 19 */ 20 @Controller 21 public class SpringSessionController { 22 23 @RequestMapping("/springSession") 24 public String test(HttpServletRequest request, HttpServletResponse response) { 25 // 獲取到attrName屬性值 26 Object attrName = request.getSession().getAttribute("attrName"); 27 // 如果獲取到的獲取到attrName屬性值為空 28 if (null == attrName) { 29 // 獲取到attrName屬性值設置到session中 30 request.getSession().setAttribute("attrName", "attrValue"); 31 } 32 // 后台打印消息 33 System.out.println("80: " + attrName); 34 // 返回到jsp頁面 35 return "/ok.jsp"; 36 } 37 38 }
頁面效果如下所示:
然后發現,請求數據已經自動入庫了,時間到期后自動刪除。