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绑定的httpsession的唯一标记。
 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

 sso单点登录的入门(Session跨域、Spring-Session共享)_第1张图片

域名访问,实现session跨域的效果:

sso单点登录的入门(Session跨域、Spring-Session共享)_第2张图片

sso单点登录的入门(Session跨域、Spring-Session共享)_第3张图片

sso单点登录的入门(Session跨域、Spring-Session共享)_第4张图片

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 "1.0" encoding="UTF-8"?>
 2 "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     sso-cross-domain
 9 
10     
11         index.html
12     
13 
14     
15     
16         springSessionRepositoryFilter
17         class>org.springframework.web.filter.DelegatingFilterProxyclass>
18     
19     
20         springSessionRepositoryFilter
21         /*
22         REQUEST
23         ERROR
24     
25 
26     
27     
28         charSetFilter
29         org.springframework.web.filter.CharacterEncodingFilter
30         
31             encoding
32             UTF-8
33         
34     
35     
36         charSetFilter
37         /*
38     
39 
40     
41     
42         mvc
43         org.springframework.web.servlet.DispatcherServlet
44         
45             contextConfigLocation
46             classpath:applicationContext-mvc.xml
47         
48         1
49     
50     
51         mvc
52         /
53     
54 
55 
56 

然后配置提供数据库的表格信息:

mysql文件路径在这里,找到以后运行一下sql即可。/org/springframework/session/jdbc/schema-mysql.sql。

 1 "1.0" encoding="UTF-8"?>
 2 "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     base-package="com.bie.controller" />
16 
17     
18     
19 
20     
21     
22     
25     class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration" />
26 
27 
28     "dataSource"
29         class="org.springframework.jdbc.datasource.DriverManagerDataSource">
30         "url"
31             value="jdbc:mysql://localhost:3306/springsession?useUnicode=true&characterEncoding=UTF8">
32         "username" value="root">
33         "password" value="123456">
34         "driverClassName"
35             value="com.mysql.jdbc.Driver">
36     
37 
38     
39     <bean
40         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
41         ref="dataSource" />
42     
43 
44 

简单的控制层代码;

 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 }

页面效果如下所示:

然后发现,请求数据已经自动入库了,时间到期后自动删除。

sso单点登录的入门(Session跨域、Spring-Session共享)_第5张图片

 

待续......

你可能感兴趣的:(sso单点登录的入门(Session跨域、Spring-Session共享))