手把手教你如何玩转单点登录(SSO)

情景引入

小白:起床起床起床。。。。。。快起床
我:怎么怎么了。。。又怎么了?
小白:最近,我发现了一个奇怪的事情~!
我:说收,什么奇怪的事情了呢?
小白:我前些天,我逛了逛新浪博客,然后看了看里面的内容,感觉还挺不错的。可是,关键让我觉得不可思议的问题出现了~!
我:怎么不可思议的呢?
小白:因为我之前重来没有登录过这个网站,但是,我进了里面好多的模块,都不需要我登录,你说这是不是Bug呀?
我:哈哈哈哈,果然是小白。。那么,我来问你几个问题:
小白:好呀。。你说。。。
我:你最近有玩过新浪微博吗?或者你有用过新浪邮箱吗?
小白:微博有呀。这个每天都要看的呢~~~这个有什么关系么?
我:那么问题就解决了,其实,这并不是Bug,而是为了方便用户,对相关的应用一次性登录即可访问其他相关的应用了。
小白:啊。。。。。还有这样的技术,这是怎么做怎么做的呢?
我:这就是传说中的单点登录(SSO)。
小白:这么神奇,,,快教教教教我。。

情景分析

其实,小白还是很不错的,善于发现问题,这个可是一个好习惯哦~~其实,在我们的生活中有很多这样的事情发生,在某一个网站登录了,在某些网站访问就不需要登录,而且把我们的相关信息都进行了显示。其实,这主要就是公司为了把其相关的系统方便用户进行使用,算是一种优化吧。毕竟,登录对于我来说,还是挺烦的。这里面,其实用到的就是一种单点登录(SSO)。

(一)SSO简介

SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;用户再访问别的应用的时候就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。
webSSO又是如何呢?
用户在访问页面1的时候进行了登录,但是客户端的每个请求都是单独的连接,当客户再次访问页面2的时候,如何才能告诉Web服务器,客户刚才已经登录过了呢?浏览器和服务器之间有约定:通过使用cookie技术来维护应用的状态。Cookie是可以被Web服务器设置的字符串,并且可以保存在浏览器中。当浏览器访问了页面1时,web服务器设置了一个cookie,并将这个cookie和页面1一起返回给浏览器,浏览器接到cookie之后,就会保存起来,在它访问页面2的时候会把这个cookie也带上,Web服务器接到请求时也能读出cookie的值,根据cookie值的内容就可以判断和恢复一些用户的信息状态。Web-SSO完全可以利用Cookie技术来完成用户登录信息的保存,将浏览器中的Cookie和上文中的Ticket结合起来,完成SSO的功能。
下面又实例来分析一下上面的文字:大家就明白怎么个回事了
这里写图片描述

(二)开发环境搭建

说明:因为后续的开发都是基于这个环境的,所以需要各位首先把基础环境搭建好,要不然后续会比较麻烦。

环境:IDEA+SpringMVC+Maven

步骤

  1. 通过IDEA创建一个Maven项目,如下图所示:(当然你不用maven进行jar包的管理也是可以的)
    这里写图片描述
    注意:当我们创建一个Maven项目后,可能会发现目录下面竟然没有java文件和resource文件放的位置。那么如何进行处理呢?其实很简单,我们还是在main目录下面直接创建一个java文件夹以及resource文件。然后选择其中一个右键–》Mark Directory as-》(java文件就选择root,而resource文件就选择resource即可)
  2. 添加pom.xml的依赖,配置如下
<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.24</version>
    </dependency>

    <!--j2ee相关包 servlet、jsp、jstl-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.1</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>

    <!--mysql驱动包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.46</version>
    </dependency>

    <!--spring相关包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>4.3.13.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.13.RELEASE</version>
    </dependency>

    <!--其他需要的包-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.4</version>
    </dependency>
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
  </dependencies>
  1. 在resource文件目录下添加springmvc.xml文件,进行配置springmvc相关内容。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                         http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--启用spring的一些annotation -->
    <context:annotation-config/>

    <!-- 自动扫描该包,使SpringMVC认为包下用了@controller注解的类是控制器 -->
    <context:component-scan base-package="com.hnu.scw.controller">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--HandlerMapping 无需配置,springmvc可以默认启动-->

    <!--静态资源映射-->
    <!--本项目把静态资源放在了WEB-INF的statics目录下,资源映射如下-->
    <mvc:resources mapping="/css/**" location="/WEB-INF/statics/css/"/>
    <mvc:resources mapping="/js/**" location="/WEB-INF/statics/js/"/>
    <mvc:resources mapping="/image/**" location="/WEB-INF/statics/image/"/>

    <!-- 配置注解驱动 可以将request参数与绑定到controller参数上 -->
    <mvc:annotation-driven/>

    <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀(如果最后一个还是表示文件夹,则最后的斜杠不要漏了) 使用JSP-->
    <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/><!--设置JSP文件的目录位置-->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- springmvc文件上传需要配置的节点-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="20971500"/>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="resolveLazily" value="true"/>
    </bean>
</beans>
  1. 在resource文件目录下添加log4j.properties配置,便于调式
#配置根Logger 后面是若干个Appender
log4j.rootLogger=DEBUG,A1,R
#log4j.rootLogger=INFO,A1,R

# ConsoleAppender 输出
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n

# File 输出 一天一个文件,输出路径可以定制,一般在根路径下
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File=log.txt
log4j.appender.R.MaxFileSize=500KB
log4j.appender.R.MaxBackupIndex=10
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c] [%p] - %m%n
  1. 添加web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app  xmlns="http://java.sun.com/xml/ns/javaee"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
          version="3.0">
  <!--welcome pages-->
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <!--配置springmvc DispatcherServlet-->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!--Sources标注的文件夹下需要新建一个spring文件夹-->
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc/spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
  1. 测试环境搭建是否成功。这个就属于Springmvc的知识了,这里不多说,如果没有学习过的话,欢迎阅读我的另外一篇SpringMVC专题博文。
    https://blog.csdn.net/cs_hnu_scw/article/details/77929869

(三)同域SSO开发

同域的含义:我们在浏览网页的时候,见过很多很多种不同的网页地址,但是不知道你们有没有认真观察过。那么同域指的是什么意思呢?很简单,下面举个例子来帮助大家理解。
比如有如下几个网站的url地址:
(1)http://www.scwmytest.com/demo1/login
(2)http://www.scwmytest.com/demo2/login
(3)http://www.scwmytest.com/demo3/login
上面的这三个就是同域的地址,那么怎么区分呢?其实,很明显,我们可以看出来就是它们三者都是域名保持一致,都是www.scwmytest.com,而只有后续的地址是不一样,但它们有各自的login方法,所以肯定是属于不同的应用。所以,明白了同域的概念了吗?所以对于我们本地的时候:
http://localhost/demo1/login
http://localhost/demo2/login 它们两者也就是属于同域。
好了,既然明白同域的概念了,那么就进行接下来的SSO开发。

同域SSO开发示例步骤分析

注意:下面的代码都是模拟不同的系统,在实际开发中,应该根据不同的情况来判断系统之间的情况。而我这里主要是由于没有不同系统的搭建,所以用模拟的形式代表不同系统。
1. 项目结构如下所示:
这里写图片描述
2. 编写登录页面的JSP
说明:该JSP就是用于系统进行用户身份判定,我们在实际中也同理,不管任何的系统,当我们要进行操作的时候(比如,淘宝进行购买,微博进行发表评论……),就会让我们进行身份验证。

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2018/6/24 0024
  Time: 下午 7:02
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="/sso/login" method="post">
    <span>用户名</span><input type="text" name="username">
    <span>密码</span><input type="password" name="password">
    <input type="hidden" name="needPage" value="${judgePage}">
    <input type="submit" value="登录" name="loginsubmit">
</form>
</body>
</html>

代码分析:大家可能注意到JSP页面中有一个隐藏域,那这个到底是干什么的呢?
解析:我们知道,因为存在着两个不同的系统的时候,都有可能从不同系统进入到这个登录页面中,那么当登录之后,页面又该跳转到哪一个对应的主页呢?所以,这个隐藏域的功能,就是用于存储在跳转到这个登录页面之前,用户所访问的地址,这样的话,当登录成功,就自然而然的可以回到用户所需要访问的页面中了。(示例:当我们浏览淘宝的时候,当我们点击购买物品,就会跳转到用户登录界面,然后验证成功之后,就会访问我们之前浏览的位置,道理就是类似的!)
3. 编写登录页面的处理后台
说明:主要就是处理登录页面的后台身份验证逻辑。

  • 如果用户身份验证通过,那么就把cookie添加到浏览器中,从而标识该用户是合法用户,访问其他页面则可以不用进行身份验证。
  • 如果用户身份验证失败,那么就回答登录页面,继续验证。
package com.hnu.scw.controller.simplesso;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 7:00 2018/6/24 0024
 * @ Description:同域名下的系统的登录处理
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class Simplesso {
    /**
     * 处理系统的登录逻辑
     * @return
     */
    @RequestMapping(value = "/sso/login")
    public String doLogin(HttpServletResponse httpResponse , String username , String password , String needPage ,Map<String , String > map ){
        boolean loginResult = Utils.isLogin(username, password);
        if(loginResult){
            //添加cookie到当前的浏览器中
            Cookie cookie = new Cookie("ssocookie" , "sso");
            //设置将当前的cookie存入到顶级目录(这是同级sso登录的关键)
            cookie.setPath("/");
            //将cookie存入到浏览器中,这样就标识是已经登录过的用户了
            httpResponse.addCookie(cookie);
            return "redirect:" + needPage;
            }
        //将需要后续跳转的页面传递到登录页面
        map.put("judgePage" , needPage);
        //如果登录失败,那么返回到登录界面
        return "login";
    }
}

说明:因为在实际的开发中都是通过进行数据库查询来验证是否合法身份,而在这里这个不是主要的知识点,所以就模拟一下进行身份验证的逻辑处理。如下所示:

package com.hnu.scw.controller.simplesso;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:13 2018/6/24 0024
 * @ Description:模拟登录验证的处理
 * @ Modified By:
 * @Version: $version$
 */
public class Utils {
    /**
     * 判断登录是否成功
     * @return
     */
    public static boolean isLogin(String username , String password){
        //模拟只有当用户名是scw,密码是123456才算登录成功
        if("scw".equals(username) && "123456".equals(password)){
            return true;
        }
        return false;
    }
}

4. 编写系统一的主页
说明:这个代码其实很简单,就是进行访问系统一主页的时候(假设需要先进行登录才可以访问),判断当前用户是否已经登录过,如果登录过就可以直接访问主页,如果没有登录过,那么就要返回到登录界面进行验证身份。

package com.hnu.scw.controller.simplesso;

import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 7:57 2018/6/24 0024
 * @ Description:模拟同一个域下的一个类
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class SimpleDomin1 {
    /**
     * 模拟访问第一个项目主页面
     * @return
     */
    @RequestMapping(value = "/gotomainpage1")
    public String toMainPage(HttpServletRequest httpServletRequest , Map<String , String> map){
        //首先判断是否存在cookie值
        Cookie[] cookies = httpServletRequest.getCookies();
        if(cookies != null && cookies.length > 0){
            for (Cookie cookie: cookies) {
                if ("ssocookie".equals(cookie.getName()) && "sso".equals(cookie.getValue())) {
                    //如果有对应的cookie值,那么就说明登录过了,就能访问主页面
                    return "domin1MainPage";
                }
            }
        }
        //存储需要登录之后跳转的页面
        map.put("judgePage" , "/gotomainpage1");
        //如果没有存在对应的cookie值,那么就说明是还没有进行登录过系统,则返回登录页面
        return "login";
    }
}

5. 编写系统二的主页(其实这个和系统一类似,只是跳转的主页不同,因为属于不同的系统,主页当然不同)
说明:这个等同于系统一主页的功能,只是用于区分这是两个不同的系统的主页。这样就能代表是不同系统之间不同登录,但是能够实现相互贯通。

package com.hnu.scw.controller.simplesso;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 7:57 2018/6/24 0024
 * @ Description:${description}
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class SimpleDomin2 {
    /**
     * 模拟访问第一个项目主页面
     * @return
     */
    @RequestMapping(value = "/gotomainpage2")
    public String toMainPage(HttpServletRequest httpServletRequest , Map<String , String> map){
        //首先判断是否存在cookie值
        Cookie[] cookies = httpServletRequest.getCookies();
        if(cookies != null && cookies.length > 0){
            for (Cookie cookie: cookies) {
                if ("ssocookie".equals(cookie.getName()) && "sso".equals(cookie.getValue())) {
                    //如果有对应的cookie值,那么就说明登录过了,就能访问主页面
                    return "domin2MainPage";
                }
            }
        }
        //存储需要登录之后跳转的页面
        map.put("judgePage" , "/gotomainpage2");
        //如果没有存在对应的cookie值,那么就说明是还没有进行登录过系统,则返回登录页面
        return "login";
    }
}

6. 编写系统一的主页JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>系统一的主页面</title>
</head>
<body>
<h1>欢迎你访问系统一的主页面。。。。。。。</h1>
</body>
</html>

7. 编写系统二的主页JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>系统二的主页面</title>
</head>
<body>
<h1>欢迎你访问系统二的主页面。。。。。。。</h1>
</body>
</html>
  1. SSO效果的演示
    1. 首先,访问系统一的主页地址:
      这里写图片描述
    2. 其次,访问系统二的主页地址:
      这里写图片描述
      说明:可以看到,不管我们访问哪一个主页,都提示我们要进行登录验证。因为这时候我们是没有进行登录过的。
    3. 进行身份验证
      情况一:输入错误的用户名和密码
      这里写图片描述
      当点击“登录”,就又回到登录页面。
      情况二:输入正确的用户名和密码(默认是用户名:scw 密码 123456为正确)
      这里写图片描述
      说明:我是从访问系统一的地址跳转进行登录页面的。
    4. 由于在3中,我是从系统一主页过来,所以,登录之后就到系统一的主页了
      这里写图片描述
    5. 这时候,我们再访问以下系统二的主页,看是否要进行身份验证。
      这里写图片描述
      说明:这时候不需要我们进行登录验证了耶。。奇怪,我不是没有登录系统二的吗?怎么就可以直接访问了呢?
      很简单,这就是SSO的作用,因为系统一和系统二是同域,cookie共享,所以在系统一种进行了登录,就不需要进行再一次登录了。

知识点小结:

  • 通过上面的例子,我们就可以感受到SSO的简单应用;
  • 同域是最简单的一种情况,所以一定要慢慢的理解这里面的原因。这样对于后面的情况才能更好的理解。

(四)同父域SSO开发

同父域的含义:上面讲解了同域的概念,那么同父域又是怎么样的呢?很简单,下面举个例子来帮助大家理解。
比如有如下几个网站的url地址:
(1)http://domin1.a.com/demo1/login
(2)http://domin2.a.com/demo2/login
(3)http://checkdomin.a.com/sso/checklogin
上面的这三个就是同父域的地址,那么怎么区分呢?其实,很明显,我们可以看出来就是它们三者都是出于xxx.a.com这个域名(其中的XXX是可变的,但是后缀a.com是相同的,也就是说明他们三者是同a.com的这个父域)所以,明白了同父域的概念了吗?他们三者也就是在实际开发中就是只要保持是部署在同一个父域下面的不同系统就好了。
由于本人电脑如果同时开三个tomcat会卡爆,所以,就不以三个不同的tomcat来进行演示。而是在一个项目中,把它们三次分别放到不同的包下面,这样就模拟处于三个不同的系统之中,希望大家能够明白我的意思。
好了,既然明白同父域的概念了,那么就进行接下来的SSO开发。

同父域SSO开发示例步骤分析

特别注意:由于我是通过模拟三个系统,所以,如何使我们本地通过不同的域名访问来实现三个不同的系统效果呢?

  • 很简单,打开“我的电脑”,然后如图所示:
    这里写图片描述
  • 然后修改hosts文件的内容如下即可:
127.0.0.1  localhost
::1 localhost
127.0.0.1  localhost
127.0.0.1  domin1.a.com
127.0.0.1  domin2.a.com
127.0.0.1  checkdomin.a.com

1. 项目结构如下所示:
这里写图片描述
2. 编写登录页面的JSP
说明:功能和同域一样,但是,请认真对比同域的情况,这两个页面的区别在哪。非常重要

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="http://checkdomin.a.com:8080/sso/checklogin" method="post">
    <span>用户名</span><input type="text" name="username">
    <span>密码</span><input type="password" name="password">
    <input type="hidden" name="needPage" value="${judgePage}">
    <input type="submit" value="登录" name="loginsubmit">
</form>
</body>
</html>

代码分析:大家可能注意到JSP页面中有一个隐藏域,那这个到底是干什么的呢?
解析:
(1)这个在之前的同域已经说明了的哦(示例:当我们浏览淘宝的时候,当我们点击购买物品,就会跳转到用户登录界面,然后验证成功之后,就会访问我们之前浏览的位置,道理就是类似的!)
(2)大家注意到没有,这个form表单提交的action地址,就是我们专门进行验证身份的第三个系统的地址了。所以,这是非常要重点关注的地方。
3. 编写登录页面的处理后台
说明:主要就是处理登录页面的后台身份验证逻辑。

  • 如果用户身份验证通过,那么就把cookie添加到浏览器中(特别注意与同域之间的处理区别),从而标识该用户是合法用户,访问其他页面则可以不用进行身份验证。
  • 如果用户身份验证失败,那么就回答登录页面,继续验证。
package com.hnu.scw.controller.sameparentdomin.checkdomin.a.com;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:49 2018/6/24 0024
 * @ Description:${description}
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class CheckDomin {
    /**
     * 对不同子系统中的cookie进行验证,因为这时候不能分别在子系统中进行验证
     * 这个就是实现对子系统的验证功能
     */
    @RequestMapping(value = "/sso/checkcookie")
    public void checkCookie(String cookiename , String cookievalue , HttpServletResponse httpServletResponse) throws IOException {
        boolean checkCookie = CheckDominUtils.isCheckCookie(cookiename, cookievalue);
        //如果cookie是正确的,那么就返回个子系统一个1,就表示验证成功,否则返回0
        //注意方法是用void返回,因为现在是要回到子系统,而不是在同一个系统中了,所以不能用String
        String result = "0";
        if(checkCookie){
            result = "1";
        }
        //通过流的形式返回给子系统验证cookie的结果
        httpServletResponse.getWriter().print(result);
        httpServletResponse.getWriter().close();
    }

    /**
     * 进行登录页面
     * @return
     */
    @RequestMapping(value = "/sso/checklogin")
    public String doLogin(String username , String password , String needPage , Map<String , String> map , HttpServletResponse response){
        boolean loginResult = CheckDominUtils.isLogin(username, password);
        if(loginResult){
            //如果登录成功,那么就添加cookie,并且要设置为同父域的情况
            Cookie cookie = new Cookie("ssocookie", "sso");
            //这下面这个是关键的地方,这样同父域才可以访问
            cookie.setDomain("a.com");
            cookie.setPath("/");
            response.addCookie(cookie);
            //跳转到之前进入登录页面的那个页面(这个就实现不同的系统的主页的跳转)
            return "redirect:" + needPage;
        }
        //如果登录失败,那么就返回登录页面,并且要带上后续要跳转的页面
        map.put("judgePage" , needPage);
        return "sameparentlogin";
    }
}

说明:
(1)因为在实际的开发中都是通过进行数据库查询来验证是否合法身份,而在这里这个不是主要的知识点,所以就模拟一下进行身份验证的逻辑处理。如下所示:
(2)为什么这个登录逻辑相比之前的同域变复杂很多了呢?

原因:因为在同域的时候,它们系统都是出于同级的,而现在它们只是在同父域的情况,那么对于cookie的存储位置有发生变化,必须设置domin这个属性,另外的话,这里有个cookie验证的方法,而之前都是在各个系统进行验证,为什么呢?因为,打个比方吧。两个人甲,乙,现在有一个人给了它们看一张100块钱,但是它们都不认识钱的真假,但是他们知道这是钱。现在的办法只有一个,就是把钱给丙,让他进行验证,因为只有他知道真假,然后验证完之后,再把结果分别给甲和乙。所以,同理,当用户访问不同系统的主页的时候,如果带着cookie,但是他们系统无法进行验证了,那么只有把cookie给专门进行验证的系统,当系统验证完之后,返回结果给各自访问的系统,从而再进行后续的处理。。。是不是很简单呢?
(3)所以,下面这个代码和验证系统是处于同一个系统里面的,用于进行身份验证和cookie的验证。

package com.hnu.scw.controller.sameparentdomin.checkdomin.a.com;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:13 2018/6/24 0024
 * @ Description:模拟登录验证的处理
 * @ Modified By:
 * @Version: $version$
 */
public class CheckDominUtils {
    /**
     * 判断登录是否成功
     * @return
     */
    public static boolean isLogin(String username , String password){
        //模拟只有当用户名是scw,密码是123456才算登录成功
        if("scw".equals(username) && "123456".equals(password)){
            return true;
        }
        return false;
    }

    /**
     * 用于验证子系统中提交过来的cook是否正确
     * @param cookiename
     * @param cookievalue
     * @return
     */
    public static boolean isCheckCookie(String cookiename , String cookievalue){
        if ("ssocookie".equals(cookiename) && "sso".equals(cookievalue)) {
            return true;
        }
        return  false;
    }
}

4. 编写系统一的主页
说明:这个代码其实很简单,就是进行访问系统一主页的时候(假设需要先进行登录才可以访问),判断当前用户是否包含cookie,如果有cookie,那么就把cookie的值给验证服务器系统,让它帮助验证,再给予系统一给结果,之后再进行相应的逻辑处理即可。这就是与同域的不同地方。

package com.hnu.scw.controller.sameparentdomin.domin1.a.com;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 7:57 2018/6/24 0024
 * @ Description:模拟同一个域下的一个类
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class SameParentDomin1 {
    /**
     * 模拟访问第一个项目主页面
     * @return
     */
    @RequestMapping(value = "/sameparent/gotomainpage1")
    public String toMainPage(HttpServletRequest httpServletRequest , Map<String , String> map) throws IOException {
        //首先判断是否存在cookie值
        Cookie[] cookies = httpServletRequest.getCookies();
        if(cookies != null && cookies.length > 0){
            for (Cookie cookie: cookies) {
                if ("ssocookie".equals(cookie.getName())) {
                    String checkUrl = "http://checkdomin.a.com:8080/sso/checkcookie";
                    //进行验证cookie是否合法
                    String result = Domin1Utils.checkCookie(checkUrl, cookie.getName(), cookie.getValue());
                    //因为在验证端那边,自己设置1就表示验证成功了
                    if("1".equals(result)){
                        //如果有对应的cookie值,那么就说明登录过了,就能访问主页面
                        return "domin1MainPage";
                    }
                }
            }
        }
        //存储需要登录之后跳转的页面
        map.put("judgePage" , "http://domin1.a.com:8080/sameparent/gotomainpage1");
        //如果没有存在对应的cookie值,那么就说明是还没有进行登录过系统,则返回登录页面
        return "sameparentlogin";
    }
}

这时候系统一就需要一个工具类,用于把cookie发送给验证系统的一个方法了。

package com.hnu.scw.controller.sameparentdomin.domin1.a.com;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:42 2018/6/24 0024
 * @ Description:第一个域名下的工具类
 * @ Modified By:
 * @Version: $version$
 */
public class Domin1Utils {

    /**
     * 将得到的cookie发送到sso服务域名中进行验证cookie是否合法
     * @param url
     * @param cookieName
     * @param cookieValue
     * @return
     */
    public static String checkCookie(String url , String cookieName , String cookieValue) throws IOException {
        HttpURLConnection httpURLConnection = null;
        StringBuffer stringBuffer = new StringBuffer();
        try {
            URL checkUrl = new URL(url + "?cookiename=" + cookieName + "&cookievalue=" + cookieValue);
            httpURLConnection = (HttpURLConnection) checkUrl.openConnection();
            //设置请求的方法是用Get请求,因为要带参数
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.connect();
            InputStream inputStream = httpURLConnection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String temp = null;
            while((temp = bufferedReader.readLine()) != null){
                stringBuffer.append(temp);
            }
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(httpURLConnection != null){
                httpURLConnection.disconnect();
            }
        }
        return stringBuffer.toString();
    }
}

5. 编写系统二的主页(其实这个和系统一类似,只是跳转的主页不同,因为属于不同的系统,主页当然不同)
说明:这个等同于系统一主页的功能,只是用于区分这是两个不同的系统的主页。这样就能代表是不同系统之间不同登录,但是能够实现相互贯通。

package com.hnu.scw.controller.sameparentdomin.domin2.a.com;

import com.hnu.scw.controller.sameparentdomin.domin1.a.com.Domin1Utils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 7:57 2018/6/24 0024
 * @ Description:${description}
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class SameParentDomin2 {
    /**
     * 模拟访问第一个项目主页面
     * @return
     */
    @RequestMapping(value = "/sameparent/gotomainpage2")
    public String toMainPage(HttpServletRequest httpServletRequest , Map<String , String> map) throws IOException {
        //首先判断是否存在cookie值
        Cookie[] cookies = httpServletRequest.getCookies();
        if(cookies != null && cookies.length > 0){
            for (Cookie cookie: cookies) {
                if ("ssocookie".equals(cookie.getName())) {
                    String checkUrl = "http://checkdomin.a.com:8080/sso/checkcookie";
                    //进行验证cookie是否合法
                    String result = Domin2Utils.checkCookie(checkUrl, cookie.getName(), cookie.getValue());
                    //因为在验证端那边,自己设置1就表示验证成功了
                    if("1".equals(result)){
                        //如果有对应的cookie值,那么就说明登录过了,就能访问主页面
                        return "domin2MainPage";
                    }
                }
            }
        }
        //存储需要登录之后跳转的页面
        map.put("judgePage" , "http://domin1.a.com:8080/sameparent/gotomainpage1");
        //如果没有存在对应的cookie值,那么就说明是还没有进行登录过系统,则返回登录页面
        return "sameparentlogin";
    }
}

同理,对于系统二也需要一个向验证服务器发送cookie验证的方法。

package com.hnu.scw.controller.sameparentdomin.domin2.a.com;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:42 2018/6/24 0024
 * @ Description:第一个域名下的工具类
 * @ Modified By:
 * @Version: $version$
 */
public class Domin2Utils {

    /**
     * 将得到的cookie发送到sso服务域名中进行验证cookie是否合法
     * @param url
     * @param cookieName
     * @param cookieValue
     * @return
     */
    public static String checkCookie(String url , String cookieName , String cookieValue) throws IOException {
        HttpURLConnection httpURLConnection = null;
        StringBuffer stringBuffer = new StringBuffer();
        try {
            URL checkUrl = new URL(url + "?cookiename=" + cookieName + "&cookievalue=" + cookieValue);
            httpURLConnection = (HttpURLConnection) checkUrl.openConnection();
            //设置请求的方法是用Get请求,因为要带参数
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.connect();
            InputStream inputStream = httpURLConnection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String temp = null;
            while((temp = bufferedReader.readLine()) != null){
                stringBuffer.append(temp);
            }
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(httpURLConnection != null){
                httpURLConnection.disconnect();
            }
        }
        return stringBuffer.toString();
    }
}

6. 编写系统一的主页JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>系统一的主页面</title>
</head>
<body>
<h1>欢迎你访问系统一的主页面。。。。。。。</h1>
</body>
</html>

7. 编写系统二的主页JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>系统二的主页面</title>
</head>
<body>
<h1>欢迎你访问系统二的主页面。。。。。。。</h1>
</body>
</html>

8. SSO效果的演示
- 访问系统一的主页效果
这里写图片描述
- 访问系统二的主页效果
这里写图片描述
- 进行登录验证(从系统一进行跳转过来)
这里写图片描述
结果如下:
这里写图片描述
- 再一次访问系统二的主页
这里写图片描述
说明:哇塞,直接访问系统二的主页面,也直接访问成功了耶。。。这里面的原理就是SSO的效果。。所以,是不是感受到它的强大了呢?

知识点小结:

  • 同父域相比同域的情况要复杂,希望大家好好的理解里面的代码和我的解析,认真区分一下它们之间的区别。
  • 对于同父域的情况下,也是我们在公司开发中碰到比较多的,因为对于一个公司来说,很多都是为了便于用户熟悉都是部署在类似同父域名的这样域名。
  • 要把我上面的代码认真的分析一下。一定可以收获很多很多。相信我!

(五)不同域SSO开发

不同域的含义:上面讲解了两种情况的概念,那么不同域又是怎么样的呢?很简单,下面举个例子来帮助大家理解。
我就用实例来给大家说明情况吧。
(1)比如新浪微博
这里写图片描述
(2)比如新浪博客
这里写图片描述
大家看一下它们两者之间的区别,再与我们之前的两种情况进行对比一下,发生什么不同的地方了吗?
是的,它们不同父域了,因为一个是直接weibo.com,而另外一个是blog.sina.com。所以,这情况当然是复杂的了。别紧张,让我慢慢给大家讲解讲解,保证你一学就会。
还是如此,由于本人电脑如果同时开三个tomcat会卡爆,所以,就不以三个不同的tomcat来进行演示。而是在一个项目中,把它们三次分别放到不同的包下面,这样就模拟处于三个不同的系统之中,希望大家能够明白我的意思。
好了,既然明白不同域的概念了,那么就进行接下来的SSO开发。

不同域SSO开发示例步骤分析

特别注意:由于我是通过模拟三个系统,所以,如何使我们本地通过不同的域名访问来实现三个不同的系统效果呢?

  • 很简单,打开“我的电脑”,然后如图所示:
    这里写图片描述
  • 然后修改hosts文件的内容如下即可:
127.0.0.1  localhost
::1 localhost
127.0.0.1  localhost
127.0.0.1  domin1.a.com
127.0.0.1  domin2.b.com
127.0.0.1  checkdomin.c.com

要保证,现在它们三者就类似不同的域下面了。是不是修改起来很简单呢?
1. 项目结构如下所示:
这里写图片描述
2. 编写登录页面的JSP
说明:功能和同域一样,但是,请认真对比与前面两者的情况,这页面的区别在哪。非常重要

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="${path}" method="post">
    <span>用户名</span><input type="text" name="username">
    <span>密码</span><input type="password" name="password">
    <input type="hidden" name="needPage" value="${judgePage}">
    <input type="submit" value="登录" name="loginsubmit">
</form>

</body>
</html>

代码分析:大家可能注意到JSP页面中有一个隐藏域,那这个到底是干什么的呢?
解析:
(1)这个在之前的同域已经说明了的哦(示例:当我们浏览淘宝的时候,当我们点击购买物品,就会跳转到用户登录界面,然后验证成功之后,就会访问我们之前浏览的位置,道理就是类似的!)
(2)大家注意到没有,这个form表单提交的action地址,与前面两种情况都不一样了,这个又是为什么呢?
其实这样理解起来会比较好,打个比方,现在一个用户,访问了一个系统的主页(是没有登录过的),那么就会到验证的页面。那么,现在问题来了,到底提交到哪个系统进行验证身份呢?还是像原来一样提交到一个验证系统中去吗?当然不行了,因为,它们现在是不同的域了,如果这样的话,那cookie到时候保存的是在验证系统的服务下面了,还怎么进行cookie验证。所以,现在就需要访问哪个系统的主页,就到哪个系统里面的身份验证的方法去。所以,${path}就是代表访问之前的是哪一个系统的完整域名。
3. 编写系统一的主页方法和验证身份的方法
说明:主要就是处理登录页面的后台身份验证逻辑和访问主页的逻辑。

package com.hnu.scw.controller.acrossdomin.domin1;

import com.hnu.scw.controller.sameparentdomin.domin1.a.com.Domin1Utils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 9:59 2018/6/24 0024
 * @ Description:${description}
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class AcrossDomin1 {
    /**
     * 第一个子系统的主页面
     * @return
     */
    @RequestMapping("/across/gotomainpage1")
    public String doMainPage1(Map<String , String> map , HttpServletRequest httpServletRequest) throws IOException {
        //首先判断是否存在cookie值
        Cookie[] cookies = httpServletRequest.getCookies();
        if(cookies != null && cookies.length > 0){
            for (Cookie cookie: cookies) {
                if ("ssocookie".equals(cookie.getName())) {
                    String checkUrl = "http://checkdomin.c.com:8080/sso/across/checkcookie";
                    //进行验证cookie是否合法
                    map.put("cookiename" , cookie.getName());
                    map.put("cookievalue" , cookie.getValue());
                    String result = AcrossDomin1Utils.checkCookie(checkUrl , map);
                    //因为在验证端那边,自己设置1就表示验证成功了
                    if("1".equals(result)){
                        //如果有对应的cookie值,那么就说明登录过了,就能访问主页面
                        return "acrossdomin1MainPage";
                    }
                }
            }
        }
        map.put("path" , "http://domin1.a.com:8080/domin1/checklogin");
        //存储需要登录之后跳转的页面
        map.put("judgePage" , "http://domin1.a.com:8080/across/gotomainpage1");
        //如果没有存在对应的cookie值,那么就说明是还没有进行登录过系统,则返回登录页面
        return "acrosslogin";
    }

    @RequestMapping(value = "/domin1/checklogin")
    public String doLogindomin1(String username , String password ,String needPage , Map<String , Object> map) throws IOException {
        Map<String , String> hashmap = new HashMap<String , String >();
        hashmap.put("username" , username);
        hashmap.put("password" , password);
        //让哪一个域进行检查用户名密码这样内容(因为要保证不同子系统让一个系统域名进行检查)
        String url = "http://checkdomin.c.com:8080/sso/across/checklogin";
        String result = AcrossDomin1Utils.checkCookie(url, hashmap);
        if ("1".equals(result)){
            //添加哪些子系统都要添加对应的cookie值
            ArrayList<String> list = new ArrayList<>();
            list.add("http://domin1.a.com:8080/domin1/addcookie");
            list.add("http://domin2.b.com:8080/domin2/addcookie");
            map.put("cookiepage" , list);
            return "acrossdomin1MainPage";
        }
        map.put("path" , "http://domin1.a.com:8080/domin1/checklogin");
        map.put("judgePage" , needPage);
        return "acrosslogin";
    }

    /**
     * 添加cookie到这个系统的浏览器中
     * @param httpServletResponse
     */
    @RequestMapping(value = "/domin1/addcookie")
    public void addCookie(HttpServletResponse httpServletResponse){
        Cookie cookie = new Cookie("ssocookie", "sso");
        cookie.setPath("/");
        httpServletResponse.addCookie(cookie);
    }
}

重点分析:(请认真看和读以及分析)
(1)首先,分析doMainPage这个方法其实这个方法的功能很简单:
1:对用户访问主页进行处理。如果没有cookie,那么就进行登录页面进行处理。
2:如果这个用户已经有cookie,那么就把cookie进行封装,交给检验系统去进行检查,这个cookie的值是否正确。(这个就和同父域的情况一样,原因已经解释 过了,这里就不多说)
(2)其次,分析doLogin这个方法:
1:主要就是处理用户的登录逻辑。
2:当用户进行提交之后,把验证用户身份一样的封装给“验证系统”去进行验证,而验证系统只需要返回验证的结果就可以了,然后系统再进行后续的相应的处理。
3:如果验证成功,这边系统得到一个返回结果,那么代码里面用到了list集合进行添加不同系统的一个方法,这个又是为什么呢?
原因:我们知道,现在的系统都不是在一个域下面,而且也不同父域,它们都是出于不同的域名下面了。那么cookie不可能还是放在“验证系统”中了,因为这样,肯定是子系统是拿不到的了,那到底cookie是放在哪呢?自然,就需要每个系统都进行存放一份cookie了。可能,你们会觉得,每个系统都放一份cookie,那怎么做呢?当然,我们系统都有,那么直接验证我们是否存在就好了,这也是可以的。但是,怎么处理呢?所以,这也就是解析了,为什么这个类里面还有一个添加cookie的方法。当用户登录之后,这里就把所有的相关的需要进行SSO处理的系统的添加cookie的路径全部添加到list集合中,那么处理,自然就是在主页JSP里面进行处理了,所以,再去看卡主页的JSP里面就有一个iframe标签>然后让它隐藏的自动进行访问我们存入集合中的添加cookie中的url,那么就让各个系统都添加了cookie,这样,再访问每个系统,不就可以进行SSO不用验证了嘛。。从而为什么后面就实现了SSO!!!!!!!!!!!!(关键的关键)
下面这个代码是编写系统一用于进行处理的方法。(好好分析

package com.hnu.scw.controller.acrossdomin.domin1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:42 2018/6/24 0024
 * @ Description:第一个域名下的工具类
 * @ Modified By:
 * @Version: $version$
 */
public class AcrossDomin1Utils {

    /**
     * 将得到的cookie发送到sso服务域名中进行验证cookie是否合法
     * @param url
     * @return
     */
    public static String checkCookie(String url , Map<String , String> map) throws IOException {
        HttpURLConnection httpURLConnection = null;
        StringBuffer stringBuffer = new StringBuffer();
        try {
            StringBuffer sb = new StringBuffer();
            sb.append(url);
            sb.append("?");
            for (Map.Entry<String , String> entry: map.entrySet()) {
                sb.append(entry.getKey() + "=" + entry.getValue() + "&");
            }
            url = sb.substring(0 , sb.length() - 1);
            URL checkUrl = new URL(url );
            httpURLConnection = (HttpURLConnection) checkUrl.openConnection();
            //设置请求的方法是用Get请求,因为要带参数
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.connect();
            InputStream inputStream = httpURLConnection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String temp = null;
            while((temp = bufferedReader.readLine()) != null){
                stringBuffer.append(temp);
            }
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(httpURLConnection != null){
                httpURLConnection.disconnect();
            }
        }
        return stringBuffer.toString();
    }
}

4. 同理,编写系统二的身份验证和访问主页(类同系统一的处理)
说明:好好回头看一下系统一中,我给的重点分析的知识。非常重要

package com.hnu.scw.controller.acrossdomin.domin2;
import com.hnu.scw.controller.acrossdomin.domin1.AcrossDomin1Utils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 9:59 2018/6/24 0024
 * @ Description:${description}
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class AcrossDomin2 {
    /**
     * 第一个子系统的主页面
     * @return
     */
    @RequestMapping("/across/gotomainpage2")
    public String doMainPage1(Map<String , String> map , HttpServletRequest httpServletRequest) throws IOException {
        //首先判断是否存在cookie值
        Cookie[] cookies = httpServletRequest.getCookies();
        if(cookies != null && cookies.length > 0){
            for (Cookie cookie: cookies) {
                if ("ssocookie".equals(cookie.getName())) {
                    String checkUrl = "http://checkdomin.c.com:8080/sso/checkcookie";
                    //进行验证cookie是否合法
                    map.put("cookiename" , cookie.getName());
                    map.put("cookievalue" , cookie.getValue());
                    String result = AcrossDomin1Utils.checkCookie(checkUrl , map);
                    //因为在验证端那边,自己设置1就表示验证成功了
                    if("1".equals(result)){
                        //如果有对应的cookie值,那么就说明登录过了,就能访问主页面
                        return "acrossdomin2MainPage";
                    }
                }
            }
        }
        map.put("path" , "http://domin2.b.com:8080/domin2/checklogin");
        //存储需要登录之后跳转的页面
        map.put("judgePage" , "http://domin2.b.com:8080/across/gotomainpage2");
        //如果没有存在对应的cookie值,那么就说明是还没有进行登录过系统,则返回登录页面
        return "acrosslogin";
    }


    @RequestMapping(value = "/domin2/checklogin")
    public String doLogindomin2(String username , String password ,String needPage , Map<String , Object> map) throws IOException {
        Map<String , String> hashmap = new HashMap<String , String >();
        hashmap.put("username" , username);
        hashmap.put("password" , password);
        //让哪一个域进行检查用户名密码这样内容(因为要保证不同子系统让一个系统域名进行检查)
        String url = "http://checkdomin.c.com:8080/sso/across/checklogin";
        String result = AcrossDomin1Utils.checkCookie(url, hashmap);
        if ("1".equals(result)){
            //添加哪些子系统都要添加对应的cookie值
            ArrayList<String> list = new ArrayList<>();
            list.add("http://domin1.a.com:8080/domin1/addcookie");
            list.add("http://domin2.b.com:8080/domin2/addcookie");
            map.put("cookiepage" , list);
            return "acrossdomin2MainPage";
        }
        map.put("path" , "http://domin2.b.com:8080/domin2/checklogin");
        map.put("judgePage" , needPage);
        return "acrosslogin";
    }

    @RequestMapping(value = "/domin2/addcookie")
    public void addCookie(HttpServletResponse httpServletResponse){
        Cookie cookie = new Cookie("ssocookie", "sso");
        cookie.setPath("/");
        httpServletResponse.addCookie(cookie);
    }
}

这时候系统二同样就需要一个工具类(和系统一一样)。

package com.hnu.scw.controller.acrossdomin.domin2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:42 2018/6/24 0024
 * @ Description:第二个域名下的工具类
 * @ Modified By:
 * @Version: $version$
 */
public class AcrossDomin2Utils {

    /**
     * 将得到的cookie发送到sso服务域名中进行验证cookie是否合法
     * @param url
     * @return
     */
    public static String checkCookie(String url , Map<String , String> map) throws IOException {
        HttpURLConnection httpURLConnection = null;
        StringBuffer stringBuffer = new StringBuffer();
        try {
            StringBuffer sb = new StringBuffer();
            sb.append(url);
            sb.append("?");
            for (Map.Entry<String , String> entry: map.entrySet()) {
                sb.append(entry.getKey() + "=" + entry.getValue() + "&");
            }
            url = sb.substring(0 , sb.length() - 1);
            URL checkUrl = new URL(url );
            httpURLConnection = (HttpURLConnection) checkUrl.openConnection();
            //设置请求的方法是用Get请求,因为要带参数
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.connect();
            InputStream inputStream = httpURLConnection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String temp = null;
            while((temp = bufferedReader.readLine()) != null){
                stringBuffer.append(temp);
            }
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(httpURLConnection != null){
                httpURLConnection.disconnect();
            }
        }
        return stringBuffer.toString();
    }
}

5. 编写检验系统的相关验证代码

package com.hnu.scw.controller.acrossdomin.checkdomin;

import com.hnu.scw.controller.sameparentdomin.checkdomin.a.com.CheckDominUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:49 2018/6/24 0024
 * @ Description:${description}
 * @ Modified By:
 * @Version: $version$
 */
@Controller
public class AcrossCheckDomin {
    /**
     * 对不同子系统中的cookie进行验证,因为这时候不能分别在子系统中进行验证
     * 这个就是实现对子系统的验证功能
     */
    @RequestMapping(value = "/sso/across/checkcookie")
    public void acrosscheckCookie(String cookiename , String cookievalue , HttpServletResponse httpServletResponse) throws IOException {
        boolean checkCookie = CheckDominUtils.isCheckCookie(cookiename, cookievalue);
        //如果cookie是正确的,那么就返回个子系统一个1,就表示验证成功,否则返回0
        //注意方法是用void返回,因为现在是要回到子系统,而不是在同一个系统中了,所以不能用String
        String result = "0";
        if(checkCookie){
            result = "1";
        }
        //通过流的形式返回给子系统验证cookie的结果
        httpServletResponse.getWriter().print(result);
        httpServletResponse.getWriter().close();
    }

    /**
     * 进行验证从子系统传过来的用户名和密码是否正确
     * @return
     */
    @RequestMapping(value = "/sso/across/checklogin")
    public void doLogin(String username , String password ,   HttpServletResponse response) throws IOException {
        boolean loginResult = AcrossCheckDominUtils.isLogin(username, password);
        String result = "0";
        if(loginResult){
            result = "1";
        }
        response.getWriter().print(result);
        response.getWriter().close();
    }
}

同理,编写一个工具类,用于这个验证系统服务。

package com.hnu.scw.controller.acrossdomin.checkdomin;

/**
 * @ Author     :scw
 * @ Date       :Created in 下午 8:13 2018/6/24 0024
 * @ Description:模拟登录验证的处理
 * @ Modified By:
 * @Version: $version$
 */
public class AcrossCheckDominUtils {
    /**
     * 判断登录是否成功
     * @return
     */
    public static boolean isLogin(String username , String password){
        //模拟只有当用户名是scw,密码是123456才算登录成功
        if("scw".equals(username) && "123456".equals(password)){
            return true;
        }
        return false;
    }

    /**
     * 用于验证子系统中提交过来的cook是否正确
     * @param cookiename
     * @param cookievalue
     * @return
     */
    public static boolean isCheckCookie(String cookiename , String cookievalue){
        if ("ssocookie".equals(cookiename) && "sso".equals(cookievalue)) {
            return true;
        }
        return  false;
    }

}

6. 编写系统一的主页JSP(注意里面的细节)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <title>系统一的主页面</title>
</head>
<body>
<h1>欢迎你访问系统一的主页面。。。。。。。</h1>

<!--下面的是关键,用隐藏的方法来让每个子系统添加cookie值-->
<c:forEach var="page" items="${cookiepage}">
    <iframe src="${page}" width="0px" height="0px" style="display:none"></iframe>
</c:forEach>
</body>
</html>

7. 编写系统二的主页JSP(注意里面的细节)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>
<head>
    <title>系统二的主页面</title>
</head>
<body>
<h1>欢迎你访问系统二的主页面。。。。。。。</h1>

<!--下面的是关键,用隐藏的方法来让每个子系统添加cookie值-->
<c:forEach var="page" items="${cookiepage}">
    <iframe src="${page}" width="0px" height="0px" style="display:none"></iframe>
</c:forEach>
</body>
</html>

8. 最终的项目结构如下所示
这里写图片描述
9. SSO效果的演示

  • 访问系统一的主页效果
    这里写图片描述
  • 访问系统二的主页效果
    这里写图片描述
  • 进行登录验证(从系统一进行跳转过来)
    这里写图片描述
    结果如下:
    这里写图片描述
  • 再一次访问系统二的主页
    这里写图片描述
    说明:哇塞,直接访问系统二的主页面,也直接访问成功了耶。。。这里面的原理就是SSO的效果。。所以,是不是感受到它的强大了呢?

知识点小结:

  • 不同域的情况,是实际应用中,最多的,大家也可以进行查看,很多大公司的很多子项目都是这样的,不同域名的情况。
  • 不同域的情况最复杂,所以,需要理解起来也是最难的。希望,大家可以好好的对比三种不同的情况,认真分析,由浅到深。
  • 希望大家好好的理解我在代码以及代码后面的分析描述,这对于大家理解是非常重要的。

(六)总结

  • 上面主要是对三种不同情况的SSO开发进行了简单的讲解。如果有不懂的地方,欢迎进行交流。
  • SSO是目前开发中碰到很多的处理了,也是我们需要进行了解的一种处理技术。虽然,它关键还是在于cookie的存放和处理,但是里面包含的东西确实很多的,希望大家好好的进行分析。
  • 下面,本博文主要讲解的是SSO的知识,那么我给大家出一个题目吧,看看大家是否真的理解这个知识点以及它的扩展知识了。
    1. 用户没登陆用户名和密码,添加商品, 关闭浏览器再打开后 不登录用户名和密码 
      问:购物车商品还在吗?
    2. 用户登陆了用户名密码,添加商品,关闭浏览器再打开后 不登录用户名和密码 
      问:购物车商品还在吗?
    3. 用户登陆了用户名密码,添加商品, 关闭浏览器,然后再打开,登陆用户名和密码
      问:购物车商品还在吗?
    4. 用户登陆了用户名密码,添加商品, 关闭浏览器 外地老家打开浏览器 登陆用户名和密码
      问:购物车商品还在吗?

——-先别看结果,大胆的进行猜测一下———-
1)在
2)不在了
3)在
4)在
你答对了吗??????大家可以进行亲自试验一下的。。实践出真知

下面就是关于SSO的源代码(百度云盘)
链接:https://pan.baidu.com/s/1wTZdKfHvqxzL2GePxvlT0A 密码:czn3

猜你喜欢

转载自blog.csdn.net/cs_hnu_scw/article/details/80796020