1. 程式人生 > >手把手教你如何玩轉單點登入(SSO)

手把手教你如何玩轉單點登入(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>

(三)同域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<Stri