sso單點登錄的入門(Session跨域、Spring-Session共享)


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&amp;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 }

頁面效果如下所示:

然后發現,請求數據已經自動入庫了,時間到期后自動刪除。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM