Java_Springmvc+Hibernate5+JWT+Redis實現SSO跨域單點登錄

首先,在搞代碼之前,我們要了解什麼是JWT?

這個一開始我也不太明白,後來經過百度閱讀看了很多文章才明白,其實JWT就像一個工具,可以對字符串進行簽名(加密)和解籤(解密)以及驗證字符串。這裏只是簡單的對JWT主要功能描述一下就行,更具體的瞭解JWT需要自行百度。

第二:Token爲什麼要存放在Redis?存儲格式又是怎麼樣的?

當用戶登錄成功時,將Token放在Redis,同時設置該Redis key的過期時間 = JWT Token過期時間,那麼等這個存放某Token的key過期之後,Redis會自動刪除這個Redis key(那我就不用刻意的維護Token)。

第三:SSO登陸邏輯(這是根據自己的實際項目繪製的邏輯圖,若有問題勿噴。嘿嘿!)

 

 

SSO註銷登錄邏輯:

 

效果展示:

1.子系統A點擊登錄:

SSO驗證登錄成功後,將token存到Redis,如圖示例:

然後SSO使用ajax(jsonp)發送命令給所有子系統,讓他們添加token到本地Cookie中,然後重定向回子系統

如圖:登陸成功

查看Cookie有沒有Token?

子系統A(10.1.8.12)

子系統B(10.1.0.192);

以上是在子系統A(10.1.8.12)上完成了登錄成功,根據SSO的定義,那麼,我訪問子系統B(10.1.0.192)就應該是免登錄的,接下來驗證一下,訪問子系統B(10.1.0.192)

可以看到,在子系統A上登錄之後,在子系統B上就免登陸了,也就是成功實現了跨域SSO單點登陸

功能演示到此完畢。接下來看看我是如何實現SSO跨域單點登錄的。

 

 

1.子系統訪問受保護資源,子系統攔截器攔截請求,如下:


public class LoginFilter implements Filter {

	private final Gson gson = new Gson(); 
	String NBC_SSO_LOGIN_URL = PropertiesUtils.getProperty("NBC_SSO_SERVER_LOGIN_URL");
	String TokenLogin_HttpURL = PropertiesUtils.getProperty("NBC_SSO_SERVER_HTTPURL_FOR_TOKEN_CHECK");
	String NBC_USER_TOKEN = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN");
	String NBC_SSO_CLIENT_COOKIE_TIMEOUT = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIE_TIMEOUT");
	
	 
	 
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {

		System.out.println("============   doFilter LoginFilter   ============");
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;   
	    HttpSession session = request.getSession();
	    ServletContext application = session.getServletContext();
	    boolean IsLogin = false;
	    Object UID = session.getAttribute("UID");
	    if(UID!=null && application.getAttribute(UID.toString())!=null && application.getAttribute(UID.toString()).equals(session.getId())) {
	    	IsLogin = true;
	    } 
	    if(UID!=null) {
	    	System.err.println("=============application uid:"+application.getAttribute(UID.toString()));
	    }
	    System.err.println("IsLogin:"+IsLogin);
	    //已登陸則放行
	    if(IsLogin) {
	    	chain.doFilter(request, response);
	    }else {
	    	//校驗配置文件
		    boolean error = false;
			StringBuffer errorInfo = new StringBuffer("");;
			String errorURL =  request.getContextPath() +"/error/commonError"; 
		    String[] arr1 = new String[] {NBC_USER_TOKEN,NBC_SSO_LOGIN_URL,NBC_SSO_CLIENT_COOKIE_TIMEOUT};
		    String[] arr2 = new String[] {"NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN","NBC_SSO_SERVER_LOGIN_URL","NBC_SSO_CLIENT_COOKIE_TIMEOUT"};
		    for(int i=0;i<arr1.length;i++) {
		    	if(arr1[i]==null || arr1[i].equals("")) {
		    		error = true;
		    		errorInfo.append(errorInfo.toString().trim().equals("")?arr2[i]:("※"+arr2[i]));
		    	}
		    } 
		    if(error) {
		    	session.setAttribute("error", errorInfo);
		    	response.sendRedirect(errorURL);
		    } 
		    //配置SSO登陸鏈接
		    String returnRUL = request.getRequestURL().toString();
			String ssoLoginUrl = "";
			try {
				if(returnRUL!=null && !returnRUL.equals("")) {
					returnRUL = URLEncoder.encode(returnRUL, "UTF-8");
					if(NBC_SSO_LOGIN_URL.indexOf("?")>0) {
						ssoLoginUrl = NBC_SSO_LOGIN_URL+"&returnRUL="+returnRUL;
					}else {
						ssoLoginUrl = NBC_SSO_LOGIN_URL+"?returnRUL="+returnRUL;
					} 
				}
				 
			}catch(Exception e) {
				e.printStackTrace();
			}
			
			System.err.println("登陸攔截");
			//若請求中有token參數,則校驗token有效性
		    String token = req.getParameter("token");
		    if(token!=null && !token.equals("")) {
		    	SSOCheckResult checkResult = SsoCheck.checkToken(token, TokenLogin_HttpURL, request, response);
		    	System.err.println("tokenOK 0 ?"+checkResult.isSuccess());
		    	System.err.println(gson.toJson(checkResult));
		    	if(checkResult.isSuccess()) {
		    		session.setAttribute("IsLogin",true); 
		    		NBC_ID_UsersVO user = checkResult.getUser();
		    		if(user!=null) {
		    			System.err.println("校驗Cookie token,token有效,返回用戶信息userInfo:"+gson.toJson(user));
		    			String fuserId = String.valueOf(user.getFuserID());
		    			session.setAttribute("UID", fuserId);  
		    			session.setAttribute("UserName",user.getUserName_CHS() );  
		    			application.setAttribute(fuserId, session.getId());
		    		}
		    		chain.doFilter(request, response); 
		    	}else {
		    		CookieUtils.clearCookie(NBC_USER_TOKEN, request, response); 
		    		response.sendRedirect(ssoLoginUrl);
		    	}
		    }else { 
		    	SSOCheckResult checkResult = SsoCheck.checkToken(null, TokenLogin_HttpURL, request, response); 
		    	
		    	System.err.println("tokenOK 1 ?"+checkResult.isSuccess());
		    	System.err.println(gson.toJson(checkResult));
		    	
		    	
		    	if(checkResult.isSuccess()) { 
		    		token = checkResult.getToken();
		    		session.setAttribute("IsLogin",true); 
		    		NBC_ID_UsersVO user = checkResult.getUser();
		    		if(user!=null) {
		    			System.err.println("校驗Cookie token,token有效,返回用戶信息userInfo:"+gson.toJson(user));
		    			String fuserId = String.valueOf(user.getFuserID());
		    			session.setAttribute("UID", fuserId);  
		    			session.setAttribute("UserName",user.getUserName_CHS() );  
		    			application.setAttribute(fuserId, session.getId());
		    		}
//		    		boolean existCookie = CookieUtils.existCookie(NBC_USER_TOKEN,request);
//		    		if(!existCookie) {
//		    			CookieUtils.addCookie(NBC_USER_TOKEN, token, Integer.parseInt(NBC_SSO_CLIENT_COOKIE_TIMEOUT) , request, response);
//		    		} 
		    		chain.doFilter(request, response);
		    	}else {
		    		//clear cookie and redirect to login 
		    		response.sendRedirect(ssoLoginUrl);
		    	} 
		    }
	    }
	    
		 

	}

	@Override
	public void destroy() {
		System.out.println("============   Destroy Filter LoginFilter   ============");
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		System.out.println("============   Init Filter LoginFilter   ============");
	}
 
}

以上攔截器中涉及到使用HttpURLConnection訪問SSO校驗Token有效性。SsoCheck.java


public class SsoCheck {
	
	private static final Gson gson = new Gson();
	private static String NBC_USER_TOKEN = PropertiesUtils.getProperty("NBC_SSO_CLIENT_COOKIENAME_FOR_TOKEN");
	
	public static SSOCheckResult checkToken(String token, String httpURL,HttpServletRequest request, HttpServletResponse response) {
		System.err.println("=============== SSOCheckResult checkToken ===============");
		String clientIp = ComputerInfoUtil.getIP(request);  
		SSOCheckResult result = new SSOCheckResult(false,500,"未知錯誤",null,token);
		//如果不存在token參數,則在Cookie中查找
		if(token==null || token.equals("")) {
			//Cookie中是否有Token
	    	Cookie[] cookies = request.getCookies(); 
	    	if(cookies!=null && cookies.length>0) { 
	    		for(Cookie cookie : cookies) { 
		    		if(cookie.getName().equals(NBC_USER_TOKEN)) {
		    			token = cookie.getValue();
		    		} 
		    	}
	    	} 
		} 
    	if(token==null || token.equals("")) {
			return new SSOCheckResult(false,404,"參數錯誤[token]");
		}
    	result.setToken(token);
		String encoding = "UTF-8";
		String returnURL = request.getRequestURL().toString();
		try {
			//創建URL對象
			URL url = new URL(httpURL);
			//創建連接 
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			// true -- will setting parameters
			conn.setDoOutput(true);
			// true--will allow read in from
			conn.setDoInput(true);
			// will not use caches
			conn.setUseCaches(false);
			// setting serialized
			//conn.setRequestProperty("Content-type", "application/x-java-serialized-object");
			conn.setRequestProperty("Content-type", "application/json; charset=UTF-8");
			conn.setRequestProperty("Charsert", "UTF-8"); 
			conn.setRequestMethod("POST");// default is GET   
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("Charsert", "UTF-8"); 
			// 1 min
			conn.setConnectTimeout(60000);
			// 1 min
			conn.setReadTimeout(60000); 
			//send args
			conn.addRequestProperty("token",(token==null)?"":URLEncoder.encode( token, "UTF-8")); 
			conn.addRequestProperty("clientIp",(clientIp==null)?"":URLEncoder.encode( clientIp, "UTF-8")); 
			conn.addRequestProperty("returnURL",returnURL==null?"":URLEncoder.encode( returnURL, "UTF-8"));  
			// connect to server (tcp)
			conn.connect(); 
			//獲得響應碼
		    int code = conn.getResponseCode();//200代表Http OK 
		    String TokenOK = (conn.getHeaderField("TokenOK")==null)?"":conn.getHeaderField("TokenOK"); 
		    String userInfo = (conn.getHeaderField("userInfo")==null)?"":conn.getHeaderField("userInfo"); 

		    result.setErrorCode(code);
		    if(code==HttpURLConnection.HTTP_OK && TokenOK.equals("true")){ 
		    	userInfo = userInfo.equals("")?userInfo:URLDecoder.decode(userInfo, encoding);
		    	userInfo = userInfo.replace("\"", "").replace("\\", "\"");
		    	
		    	NBC_ID_UsersVO user = new NBC_ID_UsersVO();
		    	if(!userInfo.equals("")) { 
		    		user = gson.fromJson(userInfo, NBC_ID_UsersVO.class); 
		    	}
		    	result = new SSOCheckResult(true,0,"",user,token);
			} 
			//關閉連接 
			conn.disconnect();
		}catch(Exception e) {
			e.printStackTrace();
			result.setSuccess(false);
			result.setErrorCode(500);
			result.setErrorMsg("服務器異常:"+e.getMessage()); 
		}
		return result;
	}

}

好了,子系統的邏輯已經處理好,接下來就差SSO端來校驗子系統發出的Http校驗Token請求了。

那麼接下來,需要創建SSO認證中心項目。

創建springboot項目,pom.xml主要引入JWT、Jedis、HttpClient等依賴,以下是我的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.0.BUILD-SNAPSHOT</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	
	<packaging>war</packaging>
	<groupId>com.nbc</groupId>
	<artifactId>com.nbc</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>NBC_SSO</name>
	<description>Spring Boot SSO Demo</description>

	<properties>
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <activiti.version>5.23.0-RC1</activiti.version><!--5.22.0 / 5.23.0-RC1 -->
        <codehaus.jackson.version>1.9.4</codehaus.jackson.version> 
        <gson.version>2.8.2</gson.version>
        <c3p0.version>0.9.5.2</c3p0.version>
        <fasterxml.jackson.version>2.7.5</fasterxml.jackson.version>
        <hibernate.version>5.1.0.Final</hibernate.version>
        <hibernate.core.version>5.2.0.Final</hibernate.core.version>  
        <hibernate.ehcache.version>5.2.0.Final</hibernate.ehcache.version>
        <hibernate.annotations.version>5.1.0.Final</hibernate.annotations.version>  
        <hibernate.jpa.api.version>1.0.1.Final</hibernate.jpa.api.version> 
        <httpclient.version>4.5.2</httpclient.version>
        <juniversalchardet.version>1.0.3</juniversalchardet.version>
        <jedis.version>2.9.0</jedis.version>
        <junit.version>4.12</junit.version>  
        <jstl.version>1.2</jstl.version>
        <jwt.version>3.4.0</jwt.version>
        <jackson.version>2.9.9</jackson.version>
        <net.sf.json.version>2.4</net.sf.json.version>
        <org.json.version>20190722</org.json.version>
        <poi.version>3.12</poi.version>
        <pdf2dom.version>1.7</pdf2dom.version>
        <pdfbox.version>2.0.12</pdfbox.version>
        <spring.version>5.2.0.BUILD-SNAPSHOT</spring.version><!--4.3.24.RELEASE  5.2.0.BUILD-SNAPSHOT -->
        <sqlserver.version>4.0</sqlserver.version>
        <servlet.api.version>4.0.0</servlet.api.version>
        <xdocreport.version>1.0.5</xdocreport.version>
	</properties>

	<dependencies>
	     <!-- 單元測試 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
		
		<!-- gson -->
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
			<version>${gson.version}</version>
		</dependency>
		<!-- JSONObject-->
		<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>${org.json.version}</version>
		</dependency>
		<!-- jackson -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>${jackson.version}</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.version}</version>
		</dependency>
		
		<!-- pdfbox -->
		<dependency>
            <groupId>net.sf.cssbox</groupId>
            <artifactId>pdf2dom</artifactId>
            <version>${pdf2dom.version}</version> 
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>${pdfbox.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox-tools</artifactId>
            <version>${pdfbox.version}</version>
        </dependency>
        
		<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> 
		<dependency>
		    <groupId>redis.clients</groupId>
		    <artifactId>jedis</artifactId>
		    <version>${jedis.version}</version>
		</dependency> 
		 
		  
        
        
		<!-- net.sf.json -->
		<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>${net.sf.json.version}</version>
			<classifier>jdk15</classifier>
		</dependency>
		
		<!-- jwt 官網依賴 -->
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>${jwt.version}</version>
		</dependency>
		<!-- httpclient -->
        <dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>${httpclient.version}</version>
		</dependency>
		<!-- lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		
		<!-- sqlserver -->
		<dependency>
			<groupId>com.microsoft.sqlserver</groupId>
			<artifactId>sqljdbc4</artifactId>
			<version>${sqlserver.version}</version>
		</dependency>
		
		<!-- c3p0 有自動回收空閒連接功能 -->
		<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
		<dependency>
			<groupId>com.mchange</groupId>
			<artifactId>c3p0</artifactId>
			<version>${c3p0.version}</version>
		</dependency>
		
		<!-- spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>   
        
		<!-- hibernate --> 
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-core</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<!-- for JPA, use hibernate-entitymanager instead of hibernate-core -->
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-entitymanager</artifactId>
		    <version>${hibernate.version}</version>
		</dependency> 
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-osgi</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-envers</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-c3p0</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-proxool</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-infinispan</artifactId>
		    <version>${hibernate.version}</version>
		</dependency>
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-ehcache</artifactId>
		    <version>${hibernate.version}</version>
		</dependency> 
		
		
		<!-- jsp相關 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servlet.api.version}</version>
			<scope>provided</scope>
		</dependency> 
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>
		<dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jsp-api</artifactId>
        </dependency>
        
        
        <!--  spring-boot -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	    <dependency>
		   <groupId>org.springframework.boot</groupId>
		   <artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
	    <finalName>nbs-sso</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

</project>

相關依賴自行拷貝到你的項目。

如何整合Springmvc和Hibernate這個我就不多說,自己搞(因爲我的SSO已經在用了,只能展示部分核心代碼)。當然,需要完整的SSO代碼也可以聯繫我,但要有償喔(Q:2225629171)

 

SSO登陸模塊:

@RequestMapping(value="/login",method=RequestMethod.GET)
	public String to_login(Model model,HttpServletRequest req,HttpSession session) {  
		System.err.println("================ SSO Login =============="); 
		String returnURL = "";
		try {
			returnURL = req.getParameter("returnURL");
			if(returnURL!=null && !returnURL.equals("")) {
				returnURL = URLDecoder.decode(returnURL, "UTF-8");
			} 
		}catch(Exception e) {
			e.printStackTrace();
		}
		System.err.println("returnURL:"+returnURL);
		model.addAttribute("user",new z_CF_UsersVO());  
		model.addAttribute("returnURL",returnURL);  
		return "login";
	}
	
	@RequestMapping(value="/login",method=RequestMethod.POST,produces = "application/json; charset=utf-8")
	@ResponseBody
	public String do_login(Model model,HttpServletRequest req,HttpServletResponse resp,HttpSession session) 
					throws Exception {
		JSONObject json = new JSONObject();
		String sessionId = session.getId();
		String ipv4 = ComputerInfoUtil.getIP(req);
		String login_method = req.getParameter("login_method");
		String username = req.getParameter("username");
		String password = req.getParameter("password");
	
		
		//1.校驗參數
		System.err.println("執行校驗參數");
		String[] params = new String[] {login_method,username,password};
		boolean paramOK = loginService.checkParam(params);
		if(!paramOK) { 
			json.put("success", false);
			json.put("info", "參數錯誤:"+gson.toJson(params));
			return json.toString();
		}
		//2.執行登陸校驗
		PublicModel<LoginResult> result = loginService.login(sessionId,ipv4,login_method,username,password);
		if(!result.isSuccess()) {
			json.put("success", false);
			json.put("info", "執行登陸校驗時發生異常:"+result.getErrorMsg());
			return json.toString();
		}
		LoginResult loginResult = result.getObj();
		if(loginResult==null) {
			json.put("success", false);
			json.put("info", "執行登陸校驗時發生異常:loginResult=null");
			return json.toString();
		} 
		System.err.println("loginResult.getCode():"+loginResult.getCode());
		if(!String.valueOf(loginResult.getCode()).equals(String.valueOf(LoginCode.First_Login)) && !String.valueOf(loginResult.getCode()).equals(String.valueOf(LoginCode.Permit_Login))) {
			System.err.println("========= 返回錯誤信息 ==========");
			json.put("success", true);
			json.put("loginResult", gson.toJson(loginResult));
			return json.toString();
		}
		//登陸有效  
		//判斷Redis服務是否已啓動
		System.err.println("查詢Redis服務是否已啓動");
		boolean IsStarted = jedisClient.IsRedisStarted();
		if(!IsStarted) { 
			json.put("success", false);
			json.put("info", "Redis服務未啓動,請及時聯繫管理員,謝謝您的配合!");
			return json.toString();
		} 
		System.err.println("Redis服務已啓動");
		//獲取新的用戶登陸信息
		z_CF_UsersVO currentUser = new z_CF_UsersVO();//將部分信息封裝返回
		currentUser.setFuserID(loginResult.getFuserId());
		currentUser.setUserName_EN(loginResult.getUserName_EN());
		currentUser.setUserName_CHS(loginResult.getUserName_CHS()); 
		Map<String,String> claims = new HashMap<String,String>();
		claims.put("fuserId", String.valueOf(loginResult.getFuserId()));
		claims.put("userInfo",gson.toJson(currentUser));
		String newToken =  JwtUtil.genToken(claims,Long.parseLong(NBC_SSO_SERVER_JWT_TOKEN_EXPIRE));
		currentUser.setToken(newToken);
		
		//註銷該用戶在其他地方的登錄信息
		System.err.println("註銷該用戶舊的登錄信息");
		String key = "";
		String old_userInfo = "";
		try {
			if(currentUser!=null) { 
				key = NBC_SSO_SERVER_JEDIS_KEY_PREFIX+String.valueOf(currentUser.getFuserID());
				if(key!=null && !key.equals("")) {
					old_userInfo = jedisClient.get(key); 
				}
				System.err.println("old_userInfo:"+old_userInfo);
				if(old_userInfo!=null && !old_userInfo.equals("")) {
					jedisClient.del(key);
				}
			}
		}catch(Exception e) {
			System.err.println("註銷用戶登陸信息時發生異常:"+e.getMessage());
			e.printStackTrace();
		}
		 
		//註冊該用戶最新的登陸信息  
        if (newToken != null && key!=null && !key.equals("")) {    
        	jedisClient.set(key , gson.toJson(currentUser));
        	jedisClient.expire(key,Integer.parseInt(NBC_SSO_SERVER_JEDIS_KEY_TIMEOUT));
        }  
        System.err.println("newToken:"+newToken);
		json.put("success", true);
		json.put("loginResult",  gson.toJson(loginResult)); 
		json.put("token", newToken);
		System.err.println("返回數據:"+json.toString());
		return json.toString(); 
 
	}

 

SSO校驗中心:(SsoCheckController.java)


@Controller
@RequestMapping("/ssocheck")
public class SsoCheckController {
	
	
	private static final Gson gson = new Gson();
	private static String NBC_SSO_SERVER_REGEX_OF_IP = PropertiesUtils.getProperty("NBC_SSO_SERVER_REGEX_OF_IP");
	private static String NBC_SSO_SERVER_JEDIS_KEY_PREFIX = PropertiesUtils.getProperty("NBC_SSO_SERVER_JEDIS_KEY_PREFIX");
	 
	 
	@Autowired
	private JedisClient jedisClient; 
	@Autowired
	private LogService logService;
	
	 
	
	
	@RequestMapping(value = "/tokencheck")
	public void ssocheck(HttpServletRequest request, HttpServletResponse response,HttpSession session) { 
		System.err.println("=============== server tokencheck ===============");
		String sessionId = session.getId();
		String token = request.getHeader("token"); 
		System.err.println("tokencheck token:"+token);
		String clientIp = request.getHeader("clientIp"); 
		String returnURL = request.getHeader("returnURL");  
		String errorURL = request.getContextPath()+"/error/commonError";
		String loginURL = request.getContextPath()+"/login?returnURL="+returnURL;
		String sonSysIp = ComputerInfoUtil.getIP(request);
		JSONObject json = new JSONObject();
		List<String> event = new ArrayList<String>();
		event.add("客戶端請求SSO認證中心校驗Token");
		try {
			//returnURL = getDecoderString(returnURL,encoding);
			if(token==null || token.equals("")) {  
				event.add("警告:token參數爲空,服務器已重定向頁面至登陸頁面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			} 
			Pattern pattern = Pattern.compile(NBC_SSO_SERVER_REGEX_OF_IP);
			if(clientIp==null || clientIp.equals("")) {
				event.add("警告:clientIp參數爲空,服務器已重定向頁面至登陸頁面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			}else if(!pattern.matcher(clientIp).matches()){
				event.add("警告:clientIp參數非法,該參數源自子系統ipv4:"+sonSysIp);
				event.add("服務器已重定向頁面至登陸頁面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			} 
			String encoding = "UTF-8";
			token = getDecoderString(token,encoding); 
			event.add("請求參數Token:"+token);
			
			boolean tokenOK = JwtUtil.checkToken(token);
			System.err.println("校驗Token:"+((tokenOK==true)?"有效":"無效"));
			if(!tokenOK) {
				event.add("警告:Token認證不通過,該客戶端存在僞造Token的可能性,請及時做好應對措施。");
				event.add("服務器已重定向頁面至登陸頁面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event);
				response.sendRedirect(loginURL);
				return;
			}
			
			String redis_userInfo = "";
			String fuserId = JwtUtil.getClaim("fuserId", token);
			if(fuserId!=null) {
				String key = NBC_SSO_SERVER_JEDIS_KEY_PREFIX+AesUtil.decode(fuserId);
				System.err.println("key:"+key);
				redis_userInfo = jedisClient.get(key); 
			} 
			System.err.println("redis_userInfo:"+redis_userInfo);
			if(redis_userInfo==null || redis_userInfo.equals("")) {
				//token不存在於服務器redis
				event.add("警告:Token不存在於服務器,服務器已重定向頁面至登陸頁面:"+loginURL); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			} 
			//token有效並且服務器有該用戶登陸信息
			//校驗token和登陸信息的token是否一致
			z_CF_UsersVO user = gson.fromJson(redis_userInfo, z_CF_UsersVO.class);
			if(!token.equals(user.getToken())) {
				event.add("token有效並且服務器有該用戶登陸信息,但token和服務器的token不一致(可能是該用戶已在其他地方登陸)"); 
				logService.ssocheckToLog(sessionId,clientIp, false, event); 
				response.sendRedirect(loginURL);
				return;
			}
			String TokenOK1 = "true";
			String userInfo = AesUtil.decode(JwtUtil.getClaim("userInfo", token)); 
			String userInfo1 = (userInfo==null)?"":URLEncoder.encode(gson.toJson(userInfo), encoding);
			json.put("TokenOK", TokenOK1);
			json.put("userInfo", userInfo);
			event.add("Token認證通過,即將返回信息給客戶端:"+json.toString()); 
			logService.ssocheckToLog(sessionId,clientIp, true, event);
			response.setHeader("TokenOK", TokenOK1);
			response.setHeader("userInfo",userInfo1);	
		}catch(Exception e) {
			try {
				String info = "SSO認證中心校驗Token時發生異常:"+e.getMessage(); 
				//checktoken記錄日誌
				event = new ArrayList<String>();
				event.add(info);
				event.add("服務器已重定向頁面至錯誤頁面:"+errorURL);
				logService.ssocheckToLog(sessionId,clientIp, false, event);
				request.getSession().setAttribute("error", info);
				response.sendRedirect(errorURL);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
		 
		 
	}
	 
	private String getEncoderString(String str,String encoding) {
		String result = "";
		try {
			if(str!=null && !str.equals("") && encoding!=null && !encoding.equals("")) {
				result = URLEncoder.encode( str, encoding); 
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return result;
	}
	
	private String getDecoderString(String str,String encoding) {
		String result = "";
		try {
			if(str!=null && !str.equals("") && encoding!=null && !encoding.equals("")) {
				result = URLDecoder.decode( str, encoding); 
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return result;
	}

}

JWT工具類:


@Slf4j
public class JwtUtil {
	
 
	
//	public static void main(String[] args) {
//		long expire = 1000 * 60;
//		Map<String, String> map = new HashMap<String, String>();
//		map.put("fuserId", "2");
//		map.put("username", "admin");
//		String token = genToken(map,expire);
//		System.err.println(token);
//		Map<String, String> claims = getClaims(token);
//		for(String key : claims.keySet()) {
//			//System.err.println(key);
//			//System.err.println(key+":"+AesUtil.decode(claims.get(key)));
//		}
//		//String claim = getClaim("fuserId",token);
//		//System.err.println(AesUtil.decode(claim));
//		
//	}

	private static final Gson gson = new Gson();
	
    //密鑰
    private static String SECRET = readSecret();;
    //發行人
    private static String ISSUER = "NBC_USER";
 
    public static String getClaim(String claimName, String token) { 
    	String[] params = new String[] {claimName,token};
    	for(String param : params) {
    		if(param==null || param.equals("")) {
    			return null;
    		}
    	}
    	try {
    		Algorithm algorithm = Algorithm.HMAC256(SECRET);
    		//解密
            JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
            DecodedJWT jwt =  verifier.verify(token); 
            Map<String, Claim> map = jwt.getClaims();
            Map<String, String> resultMap = new HashMap<>();
            map.forEach((k,v) -> resultMap.put(k, v.asString())); 
            return resultMap.get(claimName);
    	}catch(Exception e) {
    		e.printStackTrace();
    	}
    	return null;
    }
    public static Map<String, String> getClaims(String token) { 
    	
    	if(token==null || token.equals("")) {
    		return null;
    	}
    	try {
    		Algorithm algorithm = Algorithm.HMAC256(SECRET);
    		//解密
            JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
            DecodedJWT jwt =  verifier.verify(token); 
            Map<String, Claim> map = jwt.getClaims();
            Map<String, String> resultMap = new HashMap<>();
            map.forEach((k,v) -> resultMap.put(k,v.asString()));
            return resultMap; 
    	}catch(Exception e) {
    		e.printStackTrace();
    	}
    	return null;
    }
    /**
     * 生成jwt
     */
    public static String genToken(Map<String, String> claims,long expire) {
    	try { 
    		//過期時間
    		Date expireDate = new Date(System.currentTimeMillis() + expire);
        	//使用HMAC256進行加密
            Algorithm algorithm = Algorithm.HMAC256(SECRET); 
            //創建jwt
            JWTCreator.Builder builder =  JWT.create()
            		.withIssuer(ISSUER)//發行人
            		.withExpiresAt(expireDate); //過期時間點
            //傳入參數
            if(claims!=null) {
            	claims.forEach((key,value)-> {
            		String encodeValue = AesUtil.encode(value);
                    builder.withClaim(key, encodeValue);
                });
            } 
            //簽名加密
            return builder.sign(algorithm);
        } catch (Exception e) {
            //log.info("jwt 生成問題");
        } 
        return null;
    }

    /**
     * 解密jwt並驗證是否正確
     */
    public static boolean checkToken (String token) {
    	if(token==null || token.equals("")) {
    		return false;
    	} 
        try {
            //使用hmac256加密算法
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
                    .withIssuer(ISSUER)
                    .build();
            DecodedJWT decodedJWT = verifier.verify(token);
            //獲得token的頭部,載荷和簽名,只對比頭部和載荷
            String [] headPayload = token.split("\\.");
            //獲得jwt解密後頭部
            String header = decodedJWT.getHeader();
            //獲得jwt解密後載荷
            String payload = decodedJWT.getPayload(); 
            return (header.equals(headPayload[0]) && payload.equals(headPayload[1]));
        } catch (Exception e) {
            //log.info("jwt解密出現錯誤,jwt或私鑰或簽證人不正確");
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 讀取密鑰
     * @return 密鑰信息
     */
    private static String readSecret() {
    	String secret = "";
    	try {
    		Properties properties = new Properties(); 
            InputStream inputStream = ClassLoader.getSystemResourceAsStream("jwt.properties");//加載resource目錄下的配置文件
            properties.load(inputStream); 
            secret = properties.getProperty("JWT_SECRET");
    	}catch(Exception e) {
    		e.printStackTrace();
    	} 
        return secret;
    }
}

AesUtil.java:


@Slf4j
public class AesUtil {

	  
    //加密密鑰
    private static String SECRET = readSecret();;


	/**
     * 加密
     * @return 加密後內容
     */
    public static String encode (String content) {
        Key key = getKey();
        byte[] result = null;
        try{
            //創建密碼器
            Cipher cipher = Cipher.getInstance("AES");
            //初始化爲加密模式
            cipher.init(Cipher.ENCRYPT_MODE,key);
            //加密
            result = cipher.doFinal(content.getBytes("UTF-8"));
           //將二進制轉換成16進制
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < result.length; i++) {
                String hex = Integer.toHexString(result[i] & 0xFF);
                if (hex.length() == 1) {
                    hex = '0' + hex;
                }
                sb.append(hex.toUpperCase());
            }
            return  sb.toString();
        } catch (Exception e) {
            //log.info("aes加密出錯");
        }
        return null;
    }

    /**
     * 解密
     * @return 解密後內容
     */
    public static String decode (String content) {
        //將16進制轉爲二進制
        if (content.length() < 1)
            return null;
        byte[] result = new byte[content.length()/2];
        for (int i = 0;i< content.length()/2; i++) {
            int high = Integer.parseInt(content.substring(i*2, i*2+1), 16);
            int low = Integer.parseInt(content.substring(i*2+1, i*2+2), 16);
            result[i] = (byte) (high * 16 + low);
        }

        Key key = getKey();
        byte[] decrypt = null;
        try{
            //創建密碼器
            Cipher cipher = Cipher.getInstance("AES");
            //初始化爲解密模式
            cipher.init(Cipher.DECRYPT_MODE,key);
            //解密
            decrypt = cipher.doFinal(result);
        } catch (Exception e) {
            //log.info("aes解密出錯");
        }
        assert decrypt != null;
        return new String(decrypt);
    }

    /**
     * 根據私鑰內容獲得私鑰
     */
    private static Key getKey () {
        SecretKey key = null;
        try {
            //創建密鑰生成器
            KeyGenerator generator = KeyGenerator.getInstance("AES");
            //初始化密鑰
            generator.init(128,new SecureRandom(SECRET.getBytes()));
            //生成密鑰
            key = generator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return key;
    }

    /**
     * 讀取密鑰
     * @return 密鑰信息
     */
    public static String readSecret () {
    	String secret = ""; 
        try {
        	Properties properties = new Properties();
            //加載resource目錄下的配置文件
            InputStream inputStream = ClassLoader.getSystemResourceAsStream("aes.properties"); 
            properties.load(inputStream);
            secret = properties.getProperty("AES_SECRET");
        } catch (IOException e) {
            //log.info("讀取密鑰文件錯誤");
        }
        return secret;
    }

}

SSO登錄成功後,跳轉至SSO登陸成功處理頁面loginSuccess.jsp(ajax發送命令,讓子系統添加token到Cookie後,重定向頁面回子系統指定的LoginOK頁面)

@RequestMapping(value="/loginSuccess",method=RequestMethod.GET)
	public String loginSuccess(Model model,HttpServletRequest req,HttpSession session) {
		 
		String domains = "";
		String returnURL = (req.getParameter("returnURL")==null)?"":req.getParameter("returnURL");
		String token = (req.getParameter("token")==null)?"":req.getParameter("token");
		try { 
			List<z_CF_DomainVO> list = new ArrayList<z_CF_DomainVO>();
			List<z_CF_Domain> list0 = domainRepository.findAll();
			list0.forEach((domain)->{
				z_CF_DomainVO vo = new z_CF_DomainVO();
				BeanUtils.copyProperties(domain, vo);
				list.add(vo);
			});
			domains = ScriptDecoder.escape(gson.toJson(list));
		}catch(Exception e) {
			e.printStackTrace();
		}
		session.setAttribute("domains",domains ); 
		session.setAttribute("returnURL", returnURL); 
		session.setAttribute("token", token);
		return "public/loginSuccess";
	}
	
	
	@RequestMapping(value="/return",method=RequestMethod.POST)
	public String returnPage(Model model,HttpServletRequest req,HttpServletResponse resp,HttpSession session) {
		String returnURL = (req.getParameter("returnURL")==null)?"":req.getParameter("returnURL");
		String token = (req.getParameter("token")==null)?"":req.getParameter("token");
		String type = (req.getParameter("type")==null)?"":req.getParameter("type");
		session.setAttribute("type", type); 
		session.setAttribute("returnURL", returnURL); 
		session.setAttribute("token", token); 
		return "public/return";
	}

loginSuccess.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>loginSuccess</title>
<%
System.err.println("=============== SSO loginSuccess.jsp ===============");
%>
<!-- jquery -->
<script type="text/javascript" src="${pageContext.request.contextPath }/static/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
$(function(){

	var domains = $("#domains").val(); 
	var token = $("#token").val();
	var returnURL = $("#returnURL").val();

	//jsonpTest();
	sendCookie(domains,token); 
	$("#MyForm").submit();
	
	
	 
	function sendCookie(domains,token){
		if(domains!="" && domains!=undefined){
			domains = JSON.parse(unescape(domains));
			for(var i in domains){
				var domainObj = domains[i];
				//var addCookieURL = domainObj.returnURLOfLoginOK;
				var addCookieURL = domainObj.urlOfOperateCookie; 
				if(token!=null && token!=""){ 
					sendAjax(addCookieURL,token);
				} 
			}
		}
	}
	
	function sendAjax(addCookieURL,token){
		if(addCookieURL!="" && addCookieURL!=null && addCookieURL!=undefined){ 
			var url = "";
			if(addCookieURL.indexOf("?")>0){
				url = addCookieURL+"&jsonpCallback=callbackFunction&type=login&token="+token; 
			}else{
				url = addCookieURL+"?jsonpCallback=callbackFunction&type=login&token="+token; 
			}   
			$.ajax({
	            url: url,
	            type: "GET",
	            timeout:10000,//10秒超時,單位:毫秒
	            async: false,
	            processData: false, 
	            jsonp: "callback",//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(默認爲:callback)
 	            jsonpCallback:"callbackFunction",//自定義的jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名
	            dataType: "jsonp", //指定服務器返回的數據類型
	            success:function(data){
	            	
				}, 
				//ajax Exception 
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					var httpStatus = XMLHttpRequest.status;
					if(httpStatus!=200){
						addCookieLog(url,httpStatus);
					}
				/*  
				alert("XMLHttpRequest.status:"+XMLHttpRequest.status+"\n"+"XMLHttpRequest.readyState:"+XMLHttpRequest.readyState+"\n"
						 +"textStatus:"+textStatus);
					*/
				}  
	        });  
		}
	}
	
	function addCookieLog(url,httpStatus){
		 $.ajax({ 
				type:"POST",
				dataType:"json",
				timeout:10000,
				url:"/log/addCookieFiledLog",  //後臺url請求,處理傳遞的參數
				async: false,
				data:{
					url:escape(url),
					httpStatus:httpStatus
					},
				success:function(data){
				//ajax請求成功執行該函數
					//alert("添加日誌成功:"+data)
				},
				 error: function(XMLHttpRequest, textStatus, errorThrown) {
					// alert("添加日誌失敗status:"+XMLHttpRequest.status)
					/*  alert("XMLHttpRequest.status:"+XMLHttpRequest.status+"\n"+"XMLHttpRequest.readyState:"+XMLHttpRequest.readyState+"\n"
							 +"textStatus:"+textStatus); */
				}
			});
	}
	
	function jsonpTest(){
		var ajax = $.ajax({
	        type: "GET",
	        async: false,
	        timeout:10000,
	        //url: "http://10.1.0.192:30013/KMS/Test/jsonpData.jsp?jsonpCallback=callbackFunction",
	        url: "http://10.1.8.12:8082/KMS/Jsp/public/index/cookie.jsp?jsonpCallback=callbackFunction",
	        dataType: "jsonp",
	        jsonp: "callback",//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(一般默認爲:callback)
	        jsonpCallback:"callbackFunction",//自定義的jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動爲你處理數據
	        success: function(json){
	        	alert(JSON.stringify(json)); 
	        },
	        error: function(){
	            alert('jsonp fail');
	        }
	    });  
	}
 
})
</script>
 
</head>
<body>

<jsp:include page="/public/loading.jsp"/>

<form id="MyForm" action="/return" method="POST"> 
<input type="hidden" id="type" name="type" value="login" />
<input type="hidden" id="domains" name="domains" value="${domains }" />
<input type="hidden" id="returnURL" name="returnURL" value="${returnURL }" /> 
<input type="hidden" id="token" name="token" value="${token }" />
</form>
</body>
</html>
  

return.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
System.err.println("================= return.jsp =================");
String returnURL = (session.getAttribute("returnURL")==null)?"":session.getAttribute("returnURL").toString();
String token = (session.getAttribute("token")==null)?"":session.getAttribute("token").toString();
String type = (session.getAttribute("type")==null)?"":session.getAttribute("type").toString();
if(!returnURL.equals("")){  
	String returnType = "";
	switch(type.toLowerCase()){
		case "login":
			returnType = "登陸";
			if(!token.equals("")){
				if(returnURL.indexOf("?")>0){
					returnURL = returnURL+"&token="+token;
				}else{
					returnURL = returnURL+"?token="+token;
				}
			}  
			break;
		case "logout":
			returnType = "註銷"; 
			break;
		default:  
			break;
	} 
	System.err.println("類型:"+returnType);
	System.err.println("服務器即將重定向回子系統:"+returnURL);
	response.sendRedirect(returnURL);
}

%>

好了,SSO大部分核心代碼已經貼出來了,接下來就看你們的編碼功底了。有什麼相關問題也可以找我討論(q:2225629171)

本次SSO實現跨域單點登陸講解完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章