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&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 }
页面效果如下所示:
然后发现,请求数据已经自动入库了,时间到期后自动删除。
待续......