SSO單點登陸

前言:今天剛學完B站up主「楠哥教你學Java」前些日子的一個直播教學,經過錄播跟着把代碼敲了一遍,整理了一下。前半部分取自楠哥的筆記,後部分代碼和思惟導圖本身概括總結,代碼細節今天是擼不完了,明後天在多研究幾遍一些細節css

單點登陸

什麼是單點登陸:一處登陸,到處登陸,一處登出,到處登出。html

用戶只須要登陸一次就能夠訪問全部相互信任的應用系統。前端

SSO

Single Sign On 單點登陸java

企業業務整合解決方案程序員

一票通web

SSO 原理

  • 當用戶第一次訪問淘寶的時候,由於尚未登陸,會被引導到認證中心進行登陸。
  • 根據用戶提供的登陸信息,認證系統進行身份驗證,若是經過,則登陸成功,並返回給用戶一個認證的憑據(token)。
  • 當用戶訪問天貓時,就會將這個 token 帶上,做爲本身認證的憑據。
  • 應用系統接收到請求後會把 token 送到認證中心進行校驗,檢查 token 的合法性。
  • 若是經過校驗,用戶就能夠在不用再次登陸的狀況下訪問天貓了。

SSO 實現技術

Cookie 單點登陸spring

使用 Cookie 做爲媒介,存放用戶憑證。數據庫

用戶登陸淘寶以後,返回一個 token,存入客戶端的 Cookie 中,當用戶訪問天貓的時候,會自動帶上 Cookie,這樣 token 又傳給了認證中心,進行校驗。瀏覽器

分佈式 Session前端框架

一、用戶第一次登陸時,將會話信息,寫入分佈式 Session。

二、用戶再次登陸時,獲取分佈式 Session,判斷是否有登陸信息,若是沒有則返回登陸頁面。

三、Session 通常存儲到 Redis 中,由於它有持久化功能,若是分佈式 Session 宕機後,重啓以後能夠從持久化存儲中從新加載會話信息。

SSO 常見方案

OAuth2(第三方登陸受權)

第三方系統訪問主系統資源,用戶無需將主系統的帳戶告知第三方,只須要經過主系統的受權,第三方就可使用主系統的資源。

jwt

Json Web Token,是爲了在網絡應用之間傳遞信息而執行的一種,基於 JSON 的開放標準,難度較高,須要瞭解不少協議,偏向底層的東西,須要你基於 JWT 認證協議,本身開放 SSO 服務和權限控制。

CAS(不是併發的 CAS)

單點登陸的 CAS 和併發的 CAS 徹底是兩碼事

中央認證服務,Central Authentication Service

CAS 是耶魯大學發起的一個開源項目,爲 Web 應用系統提供單點登陸的解決方案,實現多個系統只須要登陸一次,無需重複登陸。

  • CAS Server
  • CAS Client

Server 和 Client 分別獨立部署,Server 主要負責認證工做

Client 負責處理對客戶端資源的訪問請求,須要登陸時,重定向到 Server 進行認證。

一、受權服務器保存一個全局 session,多個客戶端各自保存本身的 session。

二、客戶端登陸時判斷本身的 session 是否已經登陸,若是沒有登陸,則重定向到服務器進行受權(帶上本身的地址,用於回調)。

三、受權服務器判斷全局的 session 是否已經登陸,若是未登陸則重定向到登陸頁面,提供用戶登陸,登陸成功以後,受權服務器重定向到客戶端,帶上 ticket。

四、客戶端收到 ticket 後,請求服務器獲取用戶信息。

五、服務器贊成客戶端受權後,服務器保存用戶信息到全局 session,客戶端將用戶信息保存至本地 session。

手寫單點登陸系統架構

Spring Boot + Thymelaf

Thymeleaf 是個什麼?

簡單說, Thymeleaf 是一個跟 Velocity、FreeMarker 相似的模板引擎,它能夠徹底替代 JSP 。相較與其餘的模板引擎,它有以下三個極吸引人的特色:

1.Thymeleaf 在有網絡和無網絡的環境下皆可運行,即它可讓美工在瀏覽器查看頁面的靜態效果,也可讓程序員在服務器查看帶數據的動態頁面效果。這是因爲它支持 html 原型,而後在 html 標籤裏增長額外的屬性來達到模板+數據的展現方式。瀏覽器解釋 html 時會忽略未定義的標籤屬性,因此 thymeleaf 的模板能夠靜態地運行;當有數據返回到頁面時,Thymeleaf 標籤會動態地替換掉靜態內容,使頁面動態顯示。

2.Thymeleaf 開箱即用的特性。它提供標準和spring標準兩種方言,能夠直接套用模板實現JSTL、 OGNL表達式效果,避免天天套模板、該jstl、改標籤的困擾。同時開發人員也能夠擴展和建立自定義的方言。

3.Thymeleaf 提供spring標準方言和一個與 SpringMVC 完美集成的可選模塊,能夠快速的實現表單綁定、屬性編輯器、國際化等功能。

SSO(主工程) 建立三個子工程

  • taobao (客戶端)
  • tmall (客戶端)
  • server (服務端)

SSO_Client_Taobao

一、首先配置淘寶客戶端的配置文件application.yml(訪問端口、thymeleaf 模板、先後綴、標頭、編碼)

server:
  port: 8081
spring:
  thymeleaf:
    suffix: .html
    prefix: classpath:/templates/
    servlet:
      content-type: text/html
    encoding: UTF-8

二、resources下建立templates目錄,寫入index.html靜態頁面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/layui/css/layui.css}" media="all">
</head>
<body>
<div class="layui-container" style="width: 700px;height: 600px;margin-top: 0px;padding-top: 60px;">

    <h1>淘寶首頁</h1>

    <div style="margin-left: 460px; width: 200px;">
        歡迎回來!admin
        <a th:href="${serverLogoutUrl}">
            <button class="layui-btn layui-btn-warm layui-btn-radius">退出</button>
        </a>
    </div>

    <img width="700px" th:src="@{/images/taobao.png}">

</div>
</body>
</html>

三、static目錄導入layui,layui是一個前端框架

​ static目錄導入一個圖片模擬淘寶首頁

四、建立運行類TaobaoApplication

package com.janeroad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TaobaoApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaobaoApplication.class,args);
    }
}

五、建立控制器 TaobaoHandler,處理異步請求

package com.janeroad.controller;

import com.janeroad.util.SSOClientUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

[@Controller](https://my.oschina.net/u/1774615)
public class TaobaoHandler {
    @GetMapping("/taobao")
    public String index(Model model){
       model.addAttribute("serverLogoutUrl", SSOClientUtil.getServerLogoutUrl());
        return "index";
    }
    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.invalidate();
        return "redirect:/taobao";
    }
}

六、建立攔截器TaobaoInterceptor,繼承與HandelerInterceptor,用於當請求服務端頁面時判斷用戶是否登陸,若是登陸過放行,若是未登陸跳轉登陸頁面

package com.janeroad.interceptor;

import com.janeroad.util.HttpUtil;
import com.janeroad.util.SSOClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;

@Slf4j
public class TaobaoInterceptor implements HandlerInterceptor {
    
    /**
     * 功能描述: <br>
     * true 放行
     * false 不放行
     * @Param: [request, response, handler]
     * @Return: boolean
     * @Author: JaneRoad
     * @Date: 2020/3/29 15:30
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //一、判斷是否已經登錄
        HttpSession session =request.getSession();
        Boolean isLogin =(Boolean) session.getAttribute("isLogin");
        if(isLogin!=null && isLogin){
            return true;
        }
        //二、判斷 token
        String token=request.getParameter("token");
        if(!StringUtils.isEmpty(token)){
            //驗證token
            log.info("token存在,須要驗證");
            //發起驗證
            String httpUrl = SSOClientUtil.SERVER_HOST_URL+"/verify";
            HashMap<String,String> params = new HashMap<>();
            params.put("token",token);
            params.put("clientLogoutUrl",SSOClientUtil.getClientLogoutUrl());
            String isVerify = HttpUtil.sendHttpRequest(httpUrl,params);
            if("true".equals(isVerify)){
                log.info("token驗證經過,token={}",token);
                //token保存到本地Cookie
                Cookie cookie = new Cookie("token",token);
                response.addCookie(cookie);
                session.setAttribute("isLogin",true);
                return true;
            }
        }

        //三、跳轉到認證中心進行登陸
        SSOClientUtil.redirectToCheckToken(request, response);
        return false;
    }
}

七、建立工具類SSOClientUtil

package com.janeroad.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Properties;

public class SSOClientUtil {

    private static Properties properties = new Properties();
    public static String SERVER_HOST_URL;
    public static String CLIENT_HOST_URL;

    static {
        try {
            properties.load(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties"));
            SERVER_HOST_URL = properties.getProperty("server.host.url");
            CLIENT_HOST_URL = properties.getProperty("client.host.url");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //跳轉到認證中心
    public static void redirectToCheckToken(HttpServletRequest request, HttpServletResponse response){
        StringBuffer url=new StringBuffer();
        url.append(SERVER_HOST_URL)
                .append("/checkToken?redirectUrl=")
                .append(CLIENT_HOST_URL)
                .append(request.getServletPath());
        try {
            response.sendRedirect(url.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static String getServerLogoutUrl(){
        return SERVER_HOST_URL+"/logout";
    }

    public static String getClientLogoutUrl(){
        return CLIENT_HOST_URL+"/logout";
    }
}

八、resources下寫入一個配置文件sso.properties,用於記錄服務端地址

server.host.url=http://localhost:8080
client.host.url=http://localhost:8081

九、建立工具類HttpUtil

package com.janeroad.util;

import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {
    /**
     * id=1
     * name=tom
     * {id=1,name=tom} id=1&name=tom
     * @param httpUrl
     * @param params
     * @return
     */
    public static String sendHttpRequest(String httpUrl, Map<String,String> params){
        try {
            URL url = new URL(httpUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            if(params!=null && params.size()>0){
                StringBuffer stringBuffer = new StringBuffer();
                for(Map.Entry<String,String> entry:params.entrySet()){
                    stringBuffer.append("&")
                            .append(entry.getKey())
                            .append("=")
                            .append(entry.getValue());
                }
                connection.getOutputStream().write(stringBuffer.substring(1).toString().getBytes("UTF-8"));
            }
            connection.connect();
            String response = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("UTF-8"));
            return response;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
}

SSO_Server

一、首先配置服務端的配置文件application.yml(訪問端口、thymeleaf 模板、先後綴、標頭、編碼)

server:
  port: 8080
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    servlet:
      content-type: text/html
    encoding: UTF-8

二、resources下建立templates目錄,寫入index.html靜態頁面

<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/layui/css/layui.css"  media="all">
</head>
<body>
<div class="layui-container" style="width: 500px;height: 330px;margin-top: 130px;border: 1px solid #009688;padding-top: 60px;border-radius: 15px">
    <form class="layui-form" action="/login" method="post">
        <input type="hidden" name="redirectUrl" th:value="${redirectUrl}">
        <div class="layui-form-item">
            <label class="layui-form-label">用戶名</label>
            <div class="layui-inline">
                <input type="text" name="username" lay-verify="username" autocomplete="off" placeholder="請輸入用戶名" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">密碼</label>
            <div class="layui-inline">
                <input type="password" name="password" lay-verify="password" placeholder="請輸入密碼" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <button class="layui-btn" lay-submit="" lay-filter="demo2" style="margin-left: 160px;">登錄</button>
        </div>
    </form>
</div>
<script src="/layui/layui.js" charset="utf-8"></script>
<script>
    layui.use(['form'], function(){
        var form = layui.form;

        //自定義驗證規則
        form.verify({
            username: function(value){
                if(value.length == 0){
                    return '用戶名不能爲空';
                }
            }
            ,password: [/(.+){6,12}$/, '密碼必須6到12位']
        });

    });
</script>
</body>
</html>

三、同客戶端,static目錄導入layui,layui是一個前端框架

四、建立運行類ServerApplication

package com.janeroad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class,args);
    }
}

五、建立控制器 ServerHandler,處理異步請求

package com.janeroad.controller;

import com.janeroad.db.MockDB;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@Controller
@Slf4j
public class ServerHandler {
    /**
     * 功能描述: 第一次登陸驗證
     * 〈〉
     * @Param: [redirectUrl, session, model]
     * @Return: java.lang.String
     * @Author: JaneRoad
     * @Date: 2020/3/29 16:21
     */

    @RequestMapping("/checkToken")
    public String checkToken(String redirectUrl, HttpSession session, Model model, HttpServletRequest request)
    {
        //獲取token
        String token = (String) session.getServletContext().getAttribute("token");
        if(StringUtils.isEmpty(token)){
            model.addAttribute("redirectUrl",redirectUrl);
            return "login";
        }else{
            //驗證token
            Cookie[] cookies = request.getCookies();
            for (Cookie cookie : cookies) {
                if(cookie.getValue().equals(token)){
                    //驗證經過,返回客戶端
                    log.info("token驗證經過");
                    return "redirect:"+redirectUrl+"?token="+token;
                }
            }
        }
        model.addAttribute("redirectUrl",redirectUrl);
        return "login";

    }

    @PostMapping("/login")
    public String login(String username,
                        String password,
                        String redirectUrl,
                        HttpSession session,
                        Model model){
        //判斷登陸
        if("admin".equals(username) && "123123".equals(password)){
            //一、建立token
            String token = UUID.randomUUID().toString();
            log.info("token建立成功!token={}",token);
            //二、token保存到全局會話中
            session.getServletContext().setAttribute("token",token);
            //三、token保存到數據庫
            MockDB.tokenSet.add(token);
            //四、返回客戶端
            return "redirect:"+redirectUrl+"?token="+token;
        }else{
            log.error("用戶名密碼錯誤!username={},password={}",username,password);
            model.addAttribute("redirectUrl",redirectUrl);
            return "login";
        }
    }

    @RequestMapping("/verify")
    @ResponseBody
    public String verifyToken(String token,String clientLogoutUrl){
        if(MockDB.tokenSet.contains(token)){
            Set<String> set = MockDB.clientLogoutUrlMap.get(token);
            if(set == null){
                set = new HashSet<>();
            }
            set.add(clientLogoutUrl);
            MockDB.clientLogoutUrlMap.put(token,set);
            return "true";
        }
        return "false";
    }

    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.invalidate();
        return "login";
    }




}

六、建立監聽器,登出的時候帳號銷燬的時候須要操做

package com.janeroad.listener;

import com.janeroad.db.MockDB;
import com.janeroad.util.HttpUtil;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.Iterator;
import java.util.Set;

@WebListener
public class SessionListener implements HttpSessionListener {
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        //一、刪除全局會話中的token
        //二、刪除數據庫的用戶信息
        //三、通知全部客戶端銷燬session
        String token = (String) se.getSession().getServletContext().getAttribute("token");
        se.getSession().getServletContext().removeAttribute("token");
        MockDB.tokenSet.remove(token);
        Set<String> set = MockDB.clientLogoutUrlMap.get(token);
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            HttpUtil.sendHttpRequest(iterator.next(),null);
        }
        MockDB.clientLogoutUrlMap.remove(token);
    }
}

七、建立監聽器配置ListenerConfig

package com.janeroad.config;

import com.janeroad.listener.SessionListener;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class ListenerConfig implements WebMvcConfigurer {

    @Bean
    public ServletListenerRegistrationBean bean(){
        ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
        bean.setListener(new SessionListener());
        return bean;
    }

}

八、建立MockDB模擬數據庫

package com.janeroad.db;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class MockDB{
    //記錄token
    public static Set<String> tokenSet=new HashSet<>();
    //客戶端登出地址
    public static Map<String,Set<String>> clientLogoutUrlMap = new HashMap<>();
}

九、建立HttpUtil工具類

package com.janeroad.util;

import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {
    /**
     * id=1
     * name=tom
     * {id=1,name=tom} id=1&name=tom
     * @param httpUrl
     * @param params
     * @return
     */
    public static String sendHttpRequest(String httpUrl, Map<String,String> params){
        try {
            URL url = new URL(httpUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            if(params!=null && params.size()>0){
                StringBuffer stringBuffer = new StringBuffer();
                for(Map.Entry<String,String> entry:params.entrySet()){
                    stringBuffer.append("&")
                            .append(entry.getKey())
                            .append("=")
                            .append(entry.getValue());
                }
                connection.getOutputStream().write(stringBuffer.substring(1).toString().getBytes("UTF-8"));
            }
            connection.connect();
            String response = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("UTF-8"));
            return response;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
}

SSO_Client——Tmall

天貓客戶端和淘寶的類似

一、首先配置淘寶客戶端的配置文件application.yml(訪問端口、thymeleaf 模板、先後綴、標頭、編碼)

server:
  port: 8082
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    servlet:
      content-type: text/html
    encoding: UTF-8

二、resources下建立templates目錄,寫入index.html靜態頁面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/layui/css/layui.css}" media="all">
</head>
<body>
<div class="layui-container" style="width: 700px;height: 600px;margin-top: 0px;padding-top: 60px;">

    <h1>天貓首頁</h1>

    <div style="margin-left: 460px; width: 200px;">
        歡迎回來!admin
        <a th:href="${serverLogoutUrl}">
            <button class="layui-btn layui-btn-warm layui-btn-radius">退出</button>
        </a>
    </div>

    <img width="700px" th:src="@{/images/tmall.png}">

</div>
</body>
</html>

三、static目錄導入layui,layui是一個前端框架。image下導入圖片,模擬天貓首頁

四、建立運行類TmallApplication

package com.janeroad;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TmallApplication {
    public static void main(String[] args) {
        SpringApplication.run(TmallApplication.class,args);
    }
}

五、建立控制器 TmallHandler,處理異步請求

package com.janeroad.controller;

import com.janeroad.util.SSOClientUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
public class TmallHandler {

    @GetMapping("/tmall")
    public String index(Model model){
        model.addAttribute("serverLogoutUrl", SSOClientUtil.getServerLogoutUrl());
        return "index";
    }

    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.invalidate();
        return "redirect:/tmall";
    }
}

六、建立攔截器TmallInterceptor,繼承與HandelerInterceptor,用於當請求服務端頁面時判斷用戶是否登陸,若是登陸過放行,若是未登陸跳轉登陸頁面

package com.janeroad.interceptor;

import com.janeroad.util.HttpUtil;
import com.janeroad.util.SSOClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;

@Slf4j
public class TmallInterceptor implements HandlerInterceptor {
    /**
     * true 放行
     * false 不放行
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //一、判斷是否已經登陸
        HttpSession session = request.getSession();
        Boolean isLogin = (Boolean) session.getAttribute("isLogin");
        if (isLogin != null && isLogin) {
            return true;
        }

        //二、判斷token
        String token = request.getParameter("token");
        if (!StringUtils.isEmpty(token)) {
            //驗證token
            log.info("token存在,須要驗證");
            //發起驗證
            String httpUrl = SSOClientUtil.SERVER_HOST_URL + "/verify";
            HashMap<String, String> params = new HashMap<>();
            params.put("token", token);
            params.put("clientLogoutUrl",SSOClientUtil.getClientLogoutUrl());
            String isVerify = HttpUtil.sendHttpRequest(httpUrl, params);
            if ("true".equals(isVerify)) {
                log.info("token驗證經過,token={}", token);
                //token保存到本地Cookie
                Cookie cookie = new Cookie("token", token);
                response.addCookie(cookie);
                session.setAttribute("isLogin", true);
                return true;
            }
        }

        //三、跳轉到認證中心進行登陸
        SSOClientUtil.redirectToCheckToken(request, response);
        return false;
    }
}

七、建立工具類SSOClientUtil

package com.janeroad.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Properties;

public class SSOClientUtil {
    private static Properties properties = new Properties();
    public static String SERVER_HOST_URL;
    public static String CLIENT_HOST_URL;

    static {
        try {
            properties.load(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties"));
            SERVER_HOST_URL = properties.getProperty("server.host.url");
            CLIENT_HOST_URL = properties.getProperty("client.host.url");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //跳轉到認證中心
    public static void redirectToCheckToken(HttpServletRequest request, HttpServletResponse response) {
        StringBuffer url = new StringBuffer();
        url.append(SERVER_HOST_URL)
                .append("/checkToken?redirectUrl=")
                .append(CLIENT_HOST_URL)
                .append(request.getServletPath());
        try {
            response.sendRedirect(url.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String getServerLogoutUrl(){
        return SERVER_HOST_URL+"/logout";
    }

    public static String getClientLogoutUrl(){
        return CLIENT_HOST_URL+"/logout";
    }
}

建立工具類HttpUtil

package com.janeroad.util;

import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {
    /**
     * id=1
     * name=tom
     * {id=1,name=tom} id=1&name=tom
     * @param httpUrl
     * @param params
     * @return
     */
    public static String sendHttpRequest(String httpUrl, Map<String,String> params){
        try {
            URL url = new URL(httpUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            if(params!=null && params.size()>0){
                StringBuffer stringBuffer = new StringBuffer();
                for(Map.Entry<String,String> entry:params.entrySet()){
                    stringBuffer.append("&")
                            .append(entry.getKey())
                            .append("=")
                            .append(entry.getValue());
                }
                connection.getOutputStream().write(stringBuffer.substring(1).toString().getBytes("UTF-8"));
            }
            connection.connect();
            String response = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("UTF-8"));
            return response;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
}

八、resources下寫入一個配置文件sso.properties,用於記錄服務端地址

server.host.url=http://localhost:8080
client.host.url=http://localhost:8082

最終實現效果

訪問localhost://8081/taobao,模擬訪問淘寶跳轉到登陸頁面

訪問localhost://8082/tmall,模擬訪問天貓跳轉到登陸頁面

淘寶輸入用戶名密碼,跳轉淘寶首頁

刷新localhost://8082/tmall,天貓自動登陸進入首頁

在天貓和淘寶任一界面退出帳號,另外一個界面隨之退出,從而實現一處登陸到處登陸,一處登出到處登出。

相關文章
相關標籤/搜索