1. 程式人生 > >SpringBoot模擬單點登入

SpringBoot模擬單點登入

SSO Single Sign On,官方的概念:web系統由單系統發展成多系統組成的應用群,複雜性應該由系統內部承擔,而不是使用者。無論web系統內部多麼複雜,對使用者而言,都是一個統一的整體,也就是說,使用者訪問web系統的整個應用群與訪問單個系統一樣,登入/登出只要一次就夠了。

簡言之,系統內部通過某種技術實現使用者統一登入和登出,所以單點登入技術一定要包括兩部分:登入、登出。

1為什麼要用單點登入?因為Cookie不能跨域。

2如何實現單點登入?

建立許可權認證中心來處理登入和登出的問題,真正提供服務的應用服務端通過Filter將鑑權任務重定向給認證中心。

原理圖:

登入:

登出:

首先看得出分為2個角色,一個是應用服務端,也就是認證客戶端;另一個是認證服務端。根據上圖來分析2個角色應該具備的功能。

認證客戶端應該具備的能力:

1必須以Filter或者外掛等形式提供,方便系統接入SSO

2未登陸的使用者重定向到SSO認證中心

3接收SSO發來的令牌並將該令牌發回給SSO做令牌認證

4處理令牌認證結果並建立區域性會話

5攔截使用者登出請求並重定向到SSO

6處理SSO發來的登出會話請求

認證服務端應該具備的能力:

0獨立的web服務

1提供登陸頁面,和對使用者的校驗

2建立全域性會話、提供token

3校驗token有效性並維護client端地址

4處理登出請求、銷燬全域性會話、並通知維護的

client端地址

5判斷使用者有沒有登入

程式碼模擬:

總共4個專案,核心的是2SSO-serverSSO-client,其中client不是獨立的web應用而是程式碼外掛。SSO-mock-app1SSO-mock-app2是安裝了外掛的2個獨立應用。

Sso-server:https://github.com/yejingtao/forblog/tree/master/sso-server

Sso-client:https://github.com/yejingtao/forblog/tree/master/sso-client

Sso-mock-app1:https://github.com/yejingtao/forblog/tree/master/sso-mock-app1

Sso-moke-app2:https://github.com/yejingtao/forblog/tree/master/sso-mock-app2

Sso-client包括3部分:filter負責判斷使用者是否登陸和重定向到sso-servercontroller負責與sso-server傳遞令牌等通訊;service維護區域性會話狀態。

Sso-server包括3部分:controller負責與sso-client傳遞令盤、校驗使用者登陸情況等通訊;service維護全域性使用者會話狀態;templates為使用者登陸提供頁面入口

下面我們按照圖中的順序來對照程式碼看一下:

1 客戶端filter判斷如果使用者沒有登入,重定向到sso-server端:

登陸過放行

if(userName!=null 
				&& String.valueOf(userName).trim().length()>0 
				&& userAccessService.checkUserStatus(userName.toString())) {
			chain.doFilter(req, response);
		}

沒登陸過重定向:

String originalUrl = req.getRequestURL().toString();
				 httpResponse.sendRedirect(ssoServerPath+"/index?originalUrl="+originalUrl+"&ssoUser="+userName);

2 sso-server端收到請求也需要判斷使用者是否登陸,使用者不存在就給到自己的登陸頁面,同時返回請求過來的連線在成功登陸後做3秒自動跳轉。

@RequestMapping("/index")
	public String firstCheck(HttpServletRequest request) {
		String originalUrl = request.getParameter("originalUrl");
		String ssoUser = request.getParameter("ssoUser");
		String token = null;
		boolean loginFlag = false;
		if(ssoUser!=null && ssoUser.trim().length()>0) {
			//對使用者先判斷是否已經登陸過
			token = authSessionService.getUserToken(ssoUser);
			if(token!=null) {
				loginFlag = true;
			}
		}
		if(loginFlag) {
			//判斷如果使用者已經在SSO-Server認證過,直接傳送token
			if(tokenTrans(request,originalUrl,ssoUser,token)) {
				if(originalUrl!=null) {
					if(originalUrl.contains("?")) {
						originalUrl = originalUrl + "&ssoUser="+ssoUser;
					}else {
						originalUrl = originalUrl + "?ssoUser="+ssoUser;
					}
				}
			}
			return "redirect:"+originalUrl;
		}else {
			//需要替換成專業點的路徑,自己登陸下了
			return "redirect:/loginPage?originalUrl="+request.getParameter("originalUrl");
		}
	}

3 使用者登陸頁面登入後,驗證使用者名稱、建立token

//登陸邏輯,返回的是令牌
	@RequestMapping(value="/doLogin",method=RequestMethod.POST)
	public String login(HttpServletRequest request, HttpServletResponse response,
			String userName, String password, String originalUrl) {
		if(authSessionService.verify(userName,password)) {
			String token = authSessionService.cacheSession(userName);
			if(tokenTrans(request,originalUrl,userName,token)) {
				//跳轉到提示成功的頁面
				request.setAttribute("helloName", userName);
				if(originalUrl!=null) {
					if(originalUrl.contains("?")) {
						originalUrl = originalUrl + "&ssoUser="+userName;
					}else {
						originalUrl = originalUrl + "?ssoUser="+userName;
					}
					request.setAttribute("originalUrl", originalUrl);
				}
			}
			return "hello";//TO-DO 三秒跳轉
		}
		//驗證不通過,重新來吧
		if(originalUrl!=null) {
			request.setAttribute("originalUrl", originalUrl);
		}
		return "loginIndex";
	}

重點是token如何傳給sso-client端並從clinet端註冊上來地址的:

4 server端將token傳給client端:

private boolean tokenTrans(HttpServletRequest request, String originalUrl,String userName, String token) {
		String[] paths = originalUrl.split("/");
		String shortAppServerUrl = paths[2];
		String returnUrl = "http://"+shortAppServerUrl+"/receiveToken?ssoToken="+token+"&userName="+userName;
		//http://peer1:8088/receiveToken?ssoToken=80414bcb-a71d-48c8-bfee-098a303324d4&userName=xixi
		return "success".equals(restTemplate.getForObject(returnUrl, String.class));
		
	}
5 client端將收到的token和自己的服務地址傳遞給server端:
@RequestMapping("/receiveToken")
	@ResponseBody
	public String receiveToken(HttpServletRequest request, String ssoToken,String userName) {
		if(ssoToken!=null && ssoToken.toString().trim().length()>0) {
			String realUrl = request.getRequestURL().toString();
			String[] paths = realUrl.split("/");
			String realUrlUrls = paths[2];
			String returnUrl = ssoServerPath+"/varifyToken?address="+realUrlUrls+"&token="+ssoToken;
			//http://peer2:8089/varifyToken?address=peer1:8088&token=c2ce29be-5adb-4aaf-82cc-2ba24330176e
			String resultStr =  restTemplate.getForObject(returnUrl, String.class);
			if("true".equals(resultStr)) {
				//建立區域性會話,儲存使用者狀態為已登陸
				userAccessService.putUserStatus(userName, resultStr);
				return "success";
			}
		}
		return "error";
	}
6 server端檢查token是否是自己發放的令牌並維護客戶端的地址
//校驗token並註冊地址
	@RequestMapping(value="/varifyToken",method=RequestMethod.GET)
	@ResponseBody
	public String varifyToken(String token, String address) {
		return String.valueOf(authSessionService.checkAndAddAddress(token, address));
	}
7 令牌互動完畢後,提示使用者登陸成功並自動跳轉回使用者的app服務頁面
onload = function(){
		setInterval(go,1000);
	};
	var x = 3;
	function go(){
		x--;
		if(x>0){
			document.getElementById('sp').innerHTML = x;
		}else{
			var returnUrl = [[${originalUrl}]];
			location.href = returnUrl;
		}
	}
8 sso-client要有根據使用者名稱發起登出的能力
@RequestMapping("/ssoLogout")
	@ResponseBody
	public String ssoLogout(String userName) {
		String userToken = userAccessService.getUserToken(userName);
		if(userToken!=null) {
			String returnUrl = ssoServerPath+"/logoutByToken?ssoToken="+userToken;
			return restTemplate.getForObject(returnUrl, String.class);
		}
		return "None Token";
	}

9 登出在sso-server端提供2種,可以用使用者登出,也可以用token登出,程式碼中都有提供這裡主要講根據token登出,我們假定登出請求都是從客戶端發起的
@RequestMapping(value="/logoutByToken",method=RequestMethod.GET)
	@ResponseBody
	public String logoutByToken(String ssoToken) {
		List<String> addressList = authSessionService.logoutByToken(ssoToken);
		if(addressList!=null) {
			addressList.stream().forEach(s -> sendLogout2Client(s,ssoToken));
		}
		return "logout";
	}
	
	private void sendLogout2Client(String address,String ssoToken) {
		String returnUrl = "http://"+address+"/ssoDeleteToken?ssoToken="+ssoToken;
		try {
			restTemplate.getForObject(returnUrl, String.class);
		}catch(Exception e) {
			//Log and do nothing
		}
	}

10  對應的客戶端要有個刪除token的接收動作
@RequestMapping("/ssoDeleteToken")
	@ResponseBody
	public String ssoDeleteToken(String ssoToken) {
		userAccessService.deleteToken(ssoToken);
		return "success";
	}
11 細節需要注意下:

a 客戶端filter過濾範圍要控制好,不要將sso-server發來的請求進行過濾,否則會死迴圈

b client端和server端的Redis要維護好usertoken

啟動測試:

Sso-mock-app1,植入sso-client外掛,域名使用peer1,服務地址peer1:9001

Sso-mock-app2,植入sso-client外掛,域名使用peer2,服務地址peer2:9002

Sso-Server,獨立部署,域名使用peer3,服務地址peer3:9003

都啟動之後,開始測試:

步驟二:在peer3,也就是SSO-Server的登陸頁面上用yejingtao賬戶登陸,登陸成功後3秒鐘後頁面自動跳轉到peer1的步驟一中的請求頁面。

步驟三:在app2服務嘗試yejingtao賬號直接訪問,成功。

步驟四:在SSO-Server端登出該賬號

步驟五:登出後app1app2又要重新登入了。

整個過程中可以配合redisclient觀察redis資料幫助自己理解。


由於只是驗證和示例程式碼,好多可以優化的地方,例如

1 token已經預留了失效時間,可以在邏輯中增加對token時效的判斷,並定時清理

2 sso-serversso-client的通訊可以改成post請求,雖然不會經過瀏覽器,從使用效果和安全級別上沒有差別,但是從http協議設計來考慮還是post更好

3 Redis序列化部分可以換成Jackson2JsonRedisSerializer節省記憶體空間

4 sso-client攔截的許可權範圍可以放在application.yml中讓使用者自己設定

如果自己產品中用SSO的話當然不用自己寫,成型有CAS等,原理當然差別不大。 還有一種解決系統間統一認證的思路,OAuth2.0,雖然也是解決多系統登入的,但是跟這裡的單點登入思路完全不一樣,等後面有機會再博文介紹。

相關推薦

SpringBoot模擬登入

SSO: Single Sign On,官方的概念:web系統由單系統發展成多系統組成的應用群,複雜性應該由系統內部承擔,而不是使用者。無論web系統內部多麼複雜,對使用者而言,都是一個統一的整體,也就是說,使用者訪問web系統的整個應用群與訪問單個系統一樣,登入/登出

Springboot+shiro登入實現

import com.ymatou.envmanagement.model.User; import com.ymatou.envmanagement.util.SessionUtil; import com.ymatou.envmanagement.util.WapperUtil; import org.a

SSO登入,簡單模擬

SSO單點登入(以下全是個人理解,如果有誤,共同批評進步) 1.什麼是單點登入: 在不同的應用中,受保護的同一使用者,登入一次就可以訪問相關的其他系統應用。比如搜狐登入後,可以直接訪問部落格、郵箱等等,而不用再重新登入部落格系統、郵箱系統等等。方便了使用者的操作。 2.同域下單點登入

SpringBoot+MyBatis+Redis實現SSO登入系統(二)

SpringBoot+MyBatis+Redis實現SSO單點登入系統(二)   三、程式碼 配置檔案配置資料庫,redis等相關的資訊。 # See http://docs.spring.io/spring-boot/docs/current/reference/html

SpringBoot+MyBatis+Redis實現SSO登入系統(一)

SpringBoot+MyBatis+Redis實現SSO單點登入系統(一)   一、SSO系統概述        SSO英文全稱Single Sign On,單點登入。SSO是在多個應用系統中,使用者只需要

基於CAS的登入SSO[5]: 基於Springboot實現CAS客戶端的前後端分離

基於CAS的單點登入SSO[5]: 基於Springboot實現CAS客戶端的前後端分離 作者:家輝,日期:2017-08-24 CSDN部落格: http://blog.csdn.net/gobitan 摘要:現在大部分系統的開發都已經

據說是springboot下實現cas的登入(但是我總感覺是MVC)----基於前後臺分離的

一、前言 前後端分離開發是目前軟體開發的主流,大大提高了開發效率  但也帶來了很多不方便之處。 1、優點:  ① 傳統全棧開發的 MVC 模式將不適合,後臺採取 MVP 面向介面程式設計,耦合度大大降低 2、缺點:  ① 跨域問題不勝其擾 3、原則:&n

Spring Boot WebSocket 節點模擬實現登入擠退

1、建立WebSocketServer @ServerEndpoint("/websocket/{sid}") @Component  // 成分、元件 public class WebSocketServer {     //靜態變數,用來記錄當前線上

Springboot 使用Redis+Session實現Session共享 , 實現登入

話說在前:   在你開啟我的這篇東西的時候,你應該學會了基於springboot專案使用Redis了,因為我不會在這篇過多去介紹·從redis基本配置。 然後, 你如果登入這模組,使用了shiro 或者說是 security ,沒關係的。 我也是結合shiro一起使用的。

CAS5.3登入服務端搭建與整合springboot

什麼是單點登入 單點登入(Single Sign On),簡稱為 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統。 SSO是概念,實現SSO需要用到CAS框架 使用cas框架實現單點登陸 有多個不同伺服器的

基於springboot和redis實現的登入

1、具體的加密和解密方法 package com.example.demo.util; import com.google.common.base.Strings; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder

SpringBoot+SpringSession+Redis實現session共享及登入

最近在學習springboot,session這個點一直困擾了我好久,今天把這些天踩的坑分享出來吧,希望能幫助更多的人。 一、pom.xml配置  <dependency> <groupId>org.springframework.boot

cas登入 (二) 客戶端與springboot整合

在springboot專案中實現cas單點登入統一認證,只需要在專案中配置 cas過濾器即可使用. 1. springboot專案pom.xml中 新增web支援依賴 、cas客戶端依賴包 <dependency> <groupId>org

(十二)Java B2B2C多使用者商城 springboot架構-SSO登入之OAuth2.0 登出流程(3)

上一篇我根據框架中OAuth2.0的使用總結,畫了一個根據使用者名稱+密碼實現OAuth2.0的登入認證的流程圖,今天我們看一下logout的流程: /** * 使用者登出 * @param accessToken * @return */ @R

(十三)java springboot b2b2c多使用者商城系統分析 - SSO登入之OAuth2.0 根據token獲取使用者資訊(4)

上一篇我根據框架中OAuth2.0的使用總結,畫了SSO單點登入之OAuth2.0 登出流程,今天我們看一下根據使用者token獲取yoghurt資訊的流程: ​ /** * 根據token獲取使用者資訊 * @param accessToken * @return * @

(十二)java springboot b2b2c shop 多使用者商城系統原始碼- SSO登入之OAuth2.0 登出流程(3)

上一篇我根據框架中OAuth2.0的使用總結,畫了一個根據使用者名稱+密碼實現OAuth2.0的登入認證的流程圖,今天我們看一下logout的流程: /** * 使用者登出 * @param accessToken * @return */

(十一)JAVA springboot ssm b2b2c多使用者商城系統 - SSO登入之OAuth2.0登入流程(2)

上一篇是站在巨人的肩膀上去研究OAuth2.0,也是為了快速幫助大家認識OAuth2.0,閒話少說,我根據框架中OAuth2.0的使用總結,畫了一個簡單的流程圖(根據使用者名稱+密碼實現OAuth2.0的登入認證):        上面的圖很清

1 springbootspringboot security OAuth的sso登入

整個工程包括三個獨立的應用,一個認證服務和兩個客戶端應用,結構非常簡單。  Maven依賴 <dependency> <groupId>org.springframework.boot</groupId> &l

springboot+security+JWT實現登入

  本次整合實現的目標:1、SSO單點登入2、基於角色和spring security註解的許可權控制。   整合過程如下:   1、使用maven構建專案,加入先關依賴,pom.xml如下: <?xml version="1.0" encoding="UTF-8"?> <project

CAS登入-客戶端整合(shiro、springboot、jwt、pac4j)(十)

CAS單點登入-客戶端整合(shiro springboot jwt pac4j)(十) 由於我們通常在業務上會有以下的使用場景: 移動端通過業務系統鑑權 移動端免登入(登入一次以後) 解決方案: JWT(token認證方案) OAuth(第三方認