前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >sso单点登录的入门(Session跨域、Spring-Session共享)

sso单点登录的入门(Session跨域、Spring-Session共享)

作者头像
别先生
发布2019-07-27 18:25:27
1.7K0
发布2019-07-27 18:25:27
举报
文章被收录于专栏:别先生别先生

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

域名访问,实现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 }

页面效果如下所示:

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

待续......

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-07-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
访问管理
访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档