1:產生原因
一些網站的背后有許多的子系統,如果每一個都要登錄的話那么不僅用戶會瘋掉,子系統也會因為這些重復的邏輯認證瘋掉,單點登錄應運而生。
2:定義
在一個多系統共存的環境下,用戶在一處登錄后,就不用在其他系統中登錄,也就是用戶的一次登錄能得到其他所有系統的信任
3:實現方式
(1、在同一個域名下):
(我們知道,PHP表單驗證是完全依賴於Cookie的)當域名相同時,按照HTTP協議規定,兩個站點是可以共享Cookie的,這樣Cookie將會分享你的信息,實現單點登錄
(2、不同域名):
這種方式需要我們借助一個單獨的SSO服務,專門做驗證用。而且我們還需要對於不同的站點的用戶要有一個統一的用戶數據。相對於前一種方式——瀏覽器需要存儲每個站點的cookie——來說,這種方式瀏覽器只需要存儲SSO服務站點的cookie信息。將這個cookie信息用於其他站點從而實現單點登錄。我們暫且將這個SSO服務站點成為www.SSOsite.com(以下簡稱SSOsite)。
在這種模型下,針對任何站點的請求都將會先重定向到SSOsite去驗證一個身份驗證cookie是否存在。如果存在,則驗證過的頁面將會發送給瀏覽器。否則用戶將會被重定向到登錄頁面。
簡單說就是:我們設計一個sso服務器,用於存儲用戶的Cookie信息,把他用來給其他的站點登錄,這樣的話所有的站點的請求都會發到sso服務器中進行驗證
實現sso服務器(主要講原理)
我把整個實現過程分為14步:
1:用戶訪問系統1的受保護資源,系統1發現用戶未登錄,跳轉至sso認證中心,並將自己的地址作為參數
2:sso認證中心發現用戶未登錄,將用戶引導至登錄頁面
3:用戶輸入用戶名密碼提交登錄申請
4:sso認證中心校驗用戶信息,創建用戶與sso認證中心之間的會話,稱為全局會話,同時創建授權令牌
5:sso認證中心帶着令牌跳轉會最初的請求地址(系統1)
6:系統1拿到令牌,去sso認證中心校驗令牌是否有效
7:sso認證中心校驗令牌,返回有效,注冊系統1
8:系統1使用該令牌創建與用戶的會話,稱為局部會話,返回受保護資源
9:用戶訪問系統2的受保護資源
10:系統2發現用戶未登錄,跳轉至sso認證中心,並將自己的地址作為參數
11:sso認證中心發現用戶已登錄,跳轉回系統2的地址,並附上令牌
12:系統2拿到令牌,去sso認證中心校驗令牌是否有效
13:sso認證中心校驗令牌,返回有效,注冊系統2
14:系統2使用該令牌創建與用戶的局部會話,返回受保護資源
這是整個實現的流程我把這個模擬的分為小三個項目:分別是 SSOServer SSOCliect1 SSOCliect2
我們用maven建好項目,搭建好基礎環境。
服務器(是兩個子系統都需要經過的,主要驗證賬號密碼,生成token和存儲token信息,方便驗證):
首先是pom
1 <?xml version="1.0"?> 2 <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4 <modelVersion>4.0.0</modelVersion> 5 <groupId>com.yzz.ssoserver</groupId> 6 <artifactId>SSOServer</artifactId> 7 <version>0.0.1-SNAPSHOT</version> 8 <packaging>war</packaging> 9 <name>SSOServer Maven Webapp</name> 10 <url>http://maven.apache.org</url> 11 <properties> 12 <spring.version>4.0.2.RELEASE</spring.version> 13 </properties> 14 <dependencies> 15 <dependency> 16 <groupId>junit</groupId> 17 <artifactId>junit</artifactId> 18 <version>3.8.1</version> 19 <scope>test</scope> 20 </dependency> 21 <dependency> 22 <groupId>org.codehaus.jackson</groupId> 23 <artifactId>jackson-mapper-asl</artifactId> 24 <version>1.9.13</version> 25 <scope>compile</scope> 26 </dependency> 27 <dependency> 28 <groupId>org.springframework</groupId> 29 <artifactId>spring-core</artifactId> 30 <version>4.0.2.RELEASE</version> 31 <scope>compile</scope> 32 </dependency> 33 <dependency> 34 <groupId>org.springframework</groupId> 35 <artifactId>spring-web</artifactId> 36 <version>4.0.2.RELEASE</version> 37 <scope>compile</scope> 38 </dependency> 39 <dependency> 40 <groupId>org.springframework</groupId> 41 <artifactId>spring-oxm</artifactId> 42 <version>4.0.2.RELEASE</version> 43 <scope>compile</scope> 44 </dependency> 45 <dependency> 46 <groupId>org.springframework</groupId> 47 <artifactId>spring-tx</artifactId> 48 <version>4.0.2.RELEASE</version> 49 <scope>compile</scope> 50 </dependency> 51 <dependency> 52 <groupId>org.springframework</groupId> 53 <artifactId>spring-jdbc</artifactId> 54 <version>4.0.2.RELEASE</version> 55 <scope>compile</scope> 56 </dependency> 57 <dependency> 58 <groupId>org.springframework</groupId> 59 <artifactId>spring-webmvc</artifactId> 60 <version>4.0.2.RELEASE</version> 61 <scope>compile</scope> 62 </dependency> 63 <dependency> 64 <groupId>org.springframework</groupId> 65 <artifactId>spring-aop</artifactId> 66 <version>4.0.2.RELEASE</version> 67 <scope>compile</scope> 68 </dependency> 69 <dependency> 70 <groupId>org.springframework</groupId> 71 <artifactId>spring-context-support</artifactId> 72 <version>4.0.2.RELEASE</version> 73 <scope>compile</scope> 74 </dependency> 75 <dependency> 76 <groupId>org.springframework</groupId> 77 <artifactId>spring-test</artifactId> 78 <version>4.0.2.RELEASE</version> 79 <scope>compile</scope> 80 </dependency> 81 <dependency> 82 <groupId>commons-httpclient</groupId> 83 <artifactId>commons-httpclient</artifactId> 84 <version>3.1</version> 85 <scope>compile</scope> 86 </dependency> 87 <dependency> 88 <groupId>commons-io</groupId> 89 <artifactId>commons-io</artifactId> 90 <version>2.4</version> 91 <scope>compile</scope> 92 </dependency> 93 <dependency> 94 <groupId>commons-codec</groupId> 95 <artifactId>commons-codec</artifactId> 96 <version>1.9</version> 97 <scope>compile</scope> 98 </dependency> 99 <dependency> 100 <groupId>commons-dbcp</groupId> 101 <artifactId>commons-dbcp</artifactId> 102 <version>1.4</version> 103 <scope>compile</scope> 104 </dependency> 105 <dependency> 106 <groupId>mysql</groupId> 107 <artifactId>mysql-connector-java</artifactId> 108 <version>5.1.30</version> 109 <scope>compile</scope> 110 </dependency> 111 <dependency> 112 <groupId>javax</groupId> 113 <artifactId>javaee-api</artifactId> 114 <version>7.0</version> 115 <scope>compile</scope> 116 </dependency> 117 <dependency> 118 <groupId>jstl</groupId> 119 <artifactId>jstl</artifactId> 120 <version>1.2</version> 121 <scope>compile</scope> 122 </dependency> 123 <dependency> 124 <groupId>com.alibaba</groupId> 125 <artifactId>fastjson</artifactId> 126 <version>1.2.24</version> 127 <scope>compile</scope> 128 </dependency> 129 </dependencies> 130 <repositories> 131 <repository> 132 <snapshots> 133 <enabled>false</enabled> 134 </snapshots> 135 <id>central</id> 136 <name>Central Repository</name> 137 <url>http://repo.maven.apache.org/maven2</url> 138 </repository> 139 </repositories> 140
然后是所必要的ssocontroller
package com.yzz.ssoserver.controller; import java.util.UUID; import javax.json.JsonObject; import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.yzz.ssoserver.util.TokenUtil; import com.yzz.ssoserver.util.UrlUtil; @Controller public class SSOServerController { //判斷用戶是否登錄,偷懶,利用controller代替攔截器 @RequestMapping("") public String loginCheck(String clientUrl,HttpServletRequest request){ String userName=(String)request.getSession().getAttribute("isLogin"); //未登錄跳轉到客戶端登錄頁面(也可以是服務器自身擁有登錄界面) if(userName==null){ System.out.println("路徑:"+clientUrl+" 未登錄,跳轉登錄頁面"); return "redirect:"+clientUrl+"?url=http://localhost:8080/SSOServer/user/login"; }else{ //以登錄攜帶令牌原路返回 String token = UUID.randomUUID().toString(); System.out.println("已經登錄,登錄賬號:"+userName+"服務端產生的token:"+token); //存儲 TokenUtil.put(token, userName); return "redirect:"+clientUrl+"?token="+token+"&allSessionId="+request.getSession().getId(); } } //令牌驗證 @ResponseBody @RequestMapping(value="/tokenCheck",method=RequestMethod.POST) public String tokenCheck(String token,String clientUrl,String allSessionId){ JSONObject j=new JSONObject(); String userName=TokenUtil.get(token); //token一次性的,用完即毀 TokenUtil.remove(token); if(userName!=null){ //設置返回消息 j.put("erroeCode", 0); j.put("header", "認證成功!"); j.put("userName", userName); //存儲地址信息,用於退出時銷毀 String url=UrlUtil.get(allSessionId); if(url==null){ url=clientUrl; }else{ url+=","+clientUrl; } UrlUtil.put(allSessionId, url); } return j.toJSONString(); } }
和usercontroller
1 package com.yzz.ssoserver.controller; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.InputStreamReader; 7 import java.io.OutputStream; 8 import java.io.OutputStreamWriter; 9 import java.net.HttpURLConnection; 10 import java.net.MalformedURLException; 11 import java.net.URL; 12 import java.util.UUID; 13 14 import javax.servlet.http.Cookie; 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 18 import org.apache.commons.httpclient.HttpClient; 19 import org.apache.commons.httpclient.HttpException; 20 import org.apache.commons.httpclient.methods.PostMethod; 21 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.stereotype.Controller; 23 import org.springframework.web.bind.annotation.RequestMapping; 24 import org.springframework.web.bind.annotation.RequestMethod; 25 import org.springframework.web.servlet.ModelAndView; 26 27 import com.yzz.ssoserver.bean.User; 28 import com.yzz.ssoserver.dao.UserDao; 29 import com.yzz.ssoserver.util.TokenUtil; 30 import com.yzz.ssoserver.util.UrlUtil; 31 32 /** 33 * 34 * @author Administrator 35 * 36 */ 37 @RequestMapping("/user") 38 @Controller 39 public class UserController { 40 @Autowired 41 private UserDao baseDao; 42 @RequestMapping("/getName") 43 public ModelAndView getName(){ 44 ModelAndView model=new ModelAndView("index"); 45 46 String userName=baseDao.getName(); 47 model.addObject("userName",userName); 48 return model; 49 } 50 51 //登錄驗證 52 @RequestMapping(value="/login",method=RequestMethod.POST) 53 public String login(HttpServletRequest request,HttpServletResponse response){ 54 ModelAndView model=new ModelAndView(); 55 String userName=request.getParameter("userName"); 56 String userPassword=request.getParameter("userPassword"); 57 String redirectUrl=request.getParameter("redirectUrl"); 58 int user=baseDao.login(userName,userPassword); 59 if(user!=0){ 60 //設置狀態(通過session判斷該瀏覽器與認證中心的全局會話是否已經建立),生成令牌 61 request.getSession().setAttribute("isLogin", userName); 62 String token = UUID.randomUUID().toString(); 63 64 //存儲 65 TokenUtil.put(token, userName); 66 /*設置cookie到瀏覽器 67 Cookie cookie=new Cookie("sso", userName); 68 cookie.setMaxAge(60); 69 response.addCookie(cookie); 70 */ 71 //將token發送給客戶端,附帶本次全局會話的sessionId 72 String allSessionId=request.getSession().getId(); 73 System.out.println("全局會話allSessionId:"+allSessionId); 74 return "redirect:"+redirectUrl+"?token="+token+"&allSessionId="+allSessionId; 75 } 76 return "redirect:http://localhost:8080/SSOServer/redirectUrl?msg=loginError"; 77 } 78 79 @RequestMapping(value="/redirectUrl",method=RequestMethod.POST) 80 public ModelAndView redirectUrl(HttpServletRequest request){ 81 ModelAndView model=new ModelAndView(); 82 String msg=request.getParameter("msg"); 83 if(msg.equals("loginError")){ 84 msg="賬號密碼錯誤"; 85 model.setViewName("error"); 86 model.addObject("msg",msg); 87 } 88 return model; 89 } 90 91 //登出 92 @RequestMapping(value="/logout") 93 public String logOut(String allSessionId,String redirectUrl,HttpServletRequest request){ 94 String url=UrlUtil.get(allSessionId); 95 UrlUtil.remove(allSessionId); 96 //刪除全局會話 97 request.getSession().removeAttribute("isLogin"); 98 99 //通知各個客戶端刪除局部會話 100 String [] urls=url.split(","); 101 //使用httpClient通知客戶端的時候發現是新建立了一個服務器與客戶端的會話,導致sessionId和客戶建立的局部會話id不相同,無法做到刪除局部會話 102 HttpClient httpClient=new HttpClient(); 103 PostMethod postMethod=new PostMethod(); 104 105 for (String u : urls) { 106 107 postMethod.setPath(u+"/logout"); 108 postMethod.addParameter("allSessionId", allSessionId); 109 110 try { 111 httpClient.executeMethod(postMethod); 112 postMethod.releaseConnection(); 113 114 } catch (HttpException e) { 115 // TODO Auto-generated catch block 116 e.printStackTrace(); 117 } catch (IOException e) { 118 // TODO Auto-generated catch block 119 e.printStackTrace(); 120 } 121 } 122 123 return "redirect:"+redirectUrl; 124 } 125 126 }
然后哦是userdao(本來連接數據庫了,但為了做實驗用我們就設置固定的值)
1 package com.yzz.ssoserver.dao; 2 3 import java.sql.ResultSet; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import javax.swing.text.html.HTMLDocument.HTMLReader.ParagraphAction; 8 import javax.swing.tree.RowMapper; 9 import javax.swing.tree.TreePath; 10 11 import org.springframework.beans.factory.annotation.Autowired; 12 import org.springframework.jdbc.core.JdbcTemplate; 13 import org.springframework.stereotype.Repository; 14 15 import com.yzz.ssoserver.bean.User; 16 import com.yzz.ssoserver.mapping.UserMapping; 17 @Repository 18 public class UserDao { 19 20 @Autowired 21 private JdbcTemplate jdbcTemplate; 22 23 24 public String getName(){ 25 return jdbcTemplate.queryForObject("select user_name from user_info where user_id=1", String.class); 26 } 27 28 public int login(String userName,String userPassword){ 29 if ("admin".equals(userName)&&"123456".equals(userPassword)) { 30 return 1; 31 } 32 //User u=new User(); 33 //String sql=" select * from user_info where user_name=? and user_password=? "; 34 35 //Object[] param= new Object[]{userName,userPassword}; 36 37 //u=jdbcTemplate.queryForObject(sql, new UserMapping(), param); 38 39 return 0; 40 } 41 42 }
然后是我們幫助類TokenUtil,UrlUtil
1 package com.yzz.ssoserver.util; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 7 public class TokenUtil { 8 9 10 public static Map<String,String> TOKEN_MAP=new HashMap<String, String>(); 11 12 public static void put(String token, String userName) { 13 TOKEN_MAP.put(token, userName); 14 15 } 16 17 public static String get(String token) { 18 return TOKEN_MAP.get(token); 19 20 } 21 22 public static void remove(String token) { 23 TOKEN_MAP.remove(token); 24 25 } 26 }
1 package com.yzz.ssoserver.util; 2 3 import java.util.HashMap; 4 5 import java.util.Map; 6 7 public class UrlUtil { 8 9 public static Map<String,String> CLIENTURL_MAP=new HashMap<String, String>(); 10 11 public static void put(String sessionId, String url) { 12 CLIENTURL_MAP.put(sessionId, url); 13 14 } 15 16 public static String get(String allSessionId) { 17 return CLIENTURL_MAP.get(allSessionId); 18 19 } 20 21 public static void remove(String sessionId) { 22 CLIENTURL_MAP.remove(sessionId); 23 24 } 25 26 27 }
在之后要去設置一下webapp下的web.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 5 version="3.0"> 6 <display-name>Archetype Created Web Application</display-name> 7 <context-param> 8 <param-name>contextConfigLocation</param-name> 9 <param-value>classpath:applicationContext.xml</param-value> 10 </context-param> 11 <!-- 編碼過濾器 --> 12 <filter> 13 <filter-name>encodingFilter</filter-name> 14 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 15 <async-supported>true</async-supported> 16 <init-param> 17 <param-name>encoding</param-name> 18 <param-value>UTF-8</param-value> 19 </init-param> 20 </filter> 21 <filter-mapping> 22 <filter-name>encodingFilter</filter-name> 23 <url-pattern>/*</url-pattern> 24 </filter-mapping> 25 <!-- Spring監聽器 --> 26 <listener> 27 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 28 </listener> 29 <!-- 防止Spring內存溢出監聽器 --> 30 <listener> 31 <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> 32 </listener> 33 34 <!-- Spring MVC servlet --> 35 <servlet> 36 <servlet-name>SpringMVC</servlet-name> 37 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 38 <init-param> 39 <param-name>contextConfigLocation</param-name> 40 <param-value>classpath:applicationContext.xml</param-value> 41 </init-param> 42 <load-on-startup>1</load-on-startup> 43 <async-supported>true</async-supported> 44 </servlet> 45 <servlet-mapping> 46 <servlet-name>SpringMVC</servlet-name> 47 <!-- 此處可以可以配置成*.do,對應struts的后綴習慣 --> 48 <url-pattern>/</url-pattern> 49 </servlet-mapping> 50 <welcome-file-list> 51 <welcome-file>/index.jsp</welcome-file> 52 </welcome-file-list> 53 54 </web-app>
這樣之后我們的服務器就配置好了,我們可以寫一個簡單的Demo可以測試一下這個maven項目有沒問題
客戶端:
和服務器一樣先是controller
1 package com.yzz.ssoclient2.controller; 2 3 import java.io.IOException; 4 import java.util.Enumeration; 5 import java.util.HashMap; 6 import java.util.Map; 7 import java.util.Set; 8 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpSession; 11 12 import org.apache.commons.httpclient.HttpClient; 13 import org.apache.commons.httpclient.HttpException; 14 import org.apache.commons.httpclient.methods.PostMethod; 15 import org.springframework.stereotype.Controller; 16 import org.springframework.ui.ModelMap; 17 import org.springframework.web.bind.annotation.RequestMapping; 18 import org.springframework.web.servlet.ModelAndView; 19 20 import com.alibaba.fastjson.JSONObject; 21 import com.yzz.ssoclient2.util.SessionUtil; 22 23 /** 24 * 25 * @author yzz 26 *客戶端部分,本想只用一個session存儲局部會話,收到服務端的退出請求后直接調用request.getSession().removeAttribute("token")清空局部會話; 27 *結果發現,服務端利用httpClient通知客戶端的時候是新建立的一個會話,此時的session和我們局部建立的session並不是同一個。 28 *解決辦法: 29 *自己維護一個session管理類,利用map將局部會話的session對象和id存儲起來。收到請求后再銷毀該session 30 */ 31 32 @Controller 33 public class SSOClientController { 34 35 //攔截所有獲取資源請求 36 @RequestMapping("") 37 public String ssoClient(HttpServletRequest request,ModelMap map){ 38 39 //判斷請求的鏈接中是否有token參數 40 String token=request.getParameter("token"); 41 String url=request.getParameter("url"); 42 43 if(token!=null){ 44 //如果有表示是認證服務器返回的 45 String allSessionId=request.getParameter("allSessionId"); 46 return "redirect:http://localhost:8080/SSOClient2/checkToken?token="+token+"&allSessionId="+allSessionId; 47 }else if(url!=null){ 48 49 return "redirect:http://localhost:8080/SSOClient2/login?url="+url; 50 }else{ 51 //其他請求,繼續判斷是否創建了和用戶之間的局部會話 52 JSONObject j=(JSONObject) request.getSession().getAttribute("token"); 53 if(j!=null){ 54 System.out.println("已經登錄,存在局部會話1:"+j); 55 System.out.println("本次局部會話的localSessionId:"+request.getSession().getId()); 56 map.addAttribute("userName", j.getString("userName")); 57 map.addAttribute("allSessionId", j.getString("allSessionId")); 58 return "index"; 59 }else{ 60 //未登錄 61 62 return "redirect:http://localhost:8080/SSOServer?clientUrl=http://localhost:8080/SSOClient2"; 63 } 64 } 65 } 66 67 //客戶端接收token並且進行驗證 68 @RequestMapping(value="/checkToken") 69 public String checkToken(HttpServletRequest request,ModelMap map){ 70 71 String token=request.getParameter("token"); 72 String allSessionId=request.getParameter("allSessionId"); 73 74 //利用httpClient進行驗證 75 String basePath = request.getScheme() + "://" + request.getServerName() + ":" 76 + request.getServerPort() + request.getContextPath(); 77 HttpClient httpClient = new HttpClient(); 78 PostMethod postMethod = new PostMethod("http://localhost:8080/SSOServer/tokenCheck"); 79 postMethod.addParameter("token", token); 80 postMethod.addParameter("allSessionId", allSessionId); 81 postMethod.addParameter("clientUrl",basePath); 82 83 try { 84 httpClient.executeMethod(postMethod); 85 String resultJson = postMethod.getResponseBodyAsString(); 86 87 postMethod.releaseConnection(); 88 //用httpClient得到的json數據默認被轉義了兩次變成了"{\\"header\\":\\"認證成功!\\",\\"userName\\":\\"admin\\",\\"erroeCode\\":0}" 89 //需要數據還原 \\" 變成 " 同時去掉前后的雙引號 90 91 resultJson=resultJson.replaceAll("\\\\\"", "\""); 92 resultJson=resultJson.substring(1, resultJson.length()-1); 93 JSONObject j=JSONObject.parseObject(resultJson); 94 j.put("allSessionId", allSessionId); 95 int errorCode=j.getIntValue("erroeCode"); 96 if(errorCode==0){ 97 //創建客戶端和用戶的局部會話 98 request.getSession().setAttribute("token", j); 99 String localSessionId=request.getSession().getId(); 100 HttpSession localSession=request.getSession(); 101 System.out.println("創建局部會話,localSessionId是:"+request.getSession().getId()); 102 map.addAttribute("userName", j.getString("userName")); 103 map.addAttribute("allSessionId", j.getString("allSessionId")); 104 //存儲局部會話 105 106 SessionUtil.setSession(localSessionId, localSession); 107 //存儲對應關系 108 SessionUtil.setLink(allSessionId, localSessionId); 109 110 }else{ 111 112 } 113 } catch (HttpException e) { 114 // TODO Auto-generated catch block 115 e.printStackTrace(); 116 } catch (IOException e) { 117 // TODO Auto-generated catch block 118 e.printStackTrace(); 119 } 120 return "index"; 121 } 122 123 //客戶端登錄 124 @RequestMapping(value="/login") 125 public ModelAndView login(HttpServletRequest request){ 126 127 String url=request.getParameter("url"); 128 ModelAndView model=new ModelAndView(); 129 model.setViewName("login"); 130 model.addObject("url", url); 131 return model; 132 } 133 134 //退出 135 @RequestMapping(value="/logout") 136 public void logout(String allSessionId){ 137 138 System.out.println("客戶端2收到退出請求"); 139 String localSessionId=SessionUtil.getLocalSessionId(allSessionId); 140 141 HttpSession localSession=SessionUtil.getSession(localSessionId); 142 143 localSession.removeAttribute("token"); 144 145 //localSession.invalidate(); 146 147 } 148 }
一個小的Util包SessionUtil
1 package com.yzz.ssoclient2.util; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 import javax.servlet.http.HttpSession; 7 public class SessionUtil { 8 9 private static Map <String, HttpSession> SESSIONMAP=new HashMap<String, HttpSession>(); 10 private static Map <String,String> sessionLink=new HashMap<String, String>(); 11 public static HttpSession getSession(String localSessionId){ 12 return SESSIONMAP.get(localSessionId); 13 } 14 15 public static void setSession(String localSessionId,HttpSession localSession){ 16 SESSIONMAP.put(localSessionId, localSession); 17 } 18 19 public static void remove(String localSessionId){ 20 SESSIONMAP.remove(localSessionId); 21 } 22 23 public static String getLocalSessionId(String allSessionId){ 24 return sessionLink.get(allSessionId); 25 } 26 public static void setLink(String allSessionId,String localSessionId){ 27 sessionLink.put(allSessionId, localSessionId); 28 } 29 public static void removeL(String allSessionId,String localSessionId){ 30 sessionLink.remove(allSessionId); 31 } 32 }
然后同樣的web設置
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 5 version="3.0"> 6 <display-name>Archetype Created Web Application</display-name> 7 <!-- Spring配置文件 --> 8 <context-param> 9 <param-name>contextConfigLocation</param-name> 10 <param-value>classpath:spring-mvc.xml</param-value> 11 </context-param> 12 <!-- 編碼過濾器 --> 13 <filter> 14 <filter-name>encodingFilter</filter-name> 15 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 16 <async-supported>true</async-supported> 17 <init-param> 18 <param-name>encoding</param-name> 19 <param-value>UTF-8</param-value> 20 </init-param> 21 </filter> 22 <filter-mapping> 23 <filter-name>encodingFilter</filter-name> 24 <url-pattern>/*</url-pattern> 25 </filter-mapping> 26 <!-- Spring監聽器 --> 27 <listener> 28 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 29 </listener> 30 <!-- 防止Spring內存溢出監聽器 --> 31 <listener> 32 <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> 33 </listener> 34 35 <!-- Spring MVC servlet --> 36 <servlet> 37 <servlet-name>SpringMVC</servlet-name> 38 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 39 <init-param> 40 <param-name>contextConfigLocation</param-name> 41 <param-value>classpath:spring-mvc.xml</param-value> 42 </init-param> 43 <load-on-startup>1</load-on-startup> 44 <async-supported>true</async-supported> 45 </servlet> 46 <servlet-mapping> 47 <servlet-name>SpringMVC</servlet-name> 48 <url-pattern>/</url-pattern> 49 </servlet-mapping> 50 <welcome-file-list> 51 <welcome-file>index.jsp</welcome-file> 52 </welcome-file-list> 53 54 </web-app>
特別說一下,我們設置下兩個頁面。登錄和資源
1 <%@ page language="java" contentType="text/html; charset=utf-8" 2 pageEncoding="utf-8"%> 3 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 4 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 5 <html xmlns='http://www.w3.org/1999/xhtml'> 6 <head> 7 <meta http-equiv='Content-Type', content='text/html; charset=utf-8'> 8 <title>單點登錄客戶端2</title> 9 <script type="text/javascript"> 10 function logout(){ 11 var allSessionId=document.getElementById("allSessionId").value; 12 13 window.location.href ="http://localhost:8080/SSOServer/user/logout?allSessionId="+allSessionId+"&redirectUrl=http://localhost:8080/SSOClient2"; 14 } 15 </script> 16 </head> 17 <body style="padding:60px;padding-bottom:40px;"> 18 19 <h2 style="color:blue">客戶端2登入后才能顯示的界面</h2> 20 21 <h3>userName is: <p style="color:blue">${userName}</p></h3> 22 <input type="hidden" id="allSessionId" value=${allSessionId}> 23 <input type="button" onclick="logout()" value="退出登錄"> 24 25 26 </body> 27 </html>
1 <%@ page language="java" contentType="text/html; charset=utf-8" 2 pageEncoding="utf-8"%> 3 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 4 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 5 <html> 6 <head> 7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 8 <title>客戶端2登錄頁面</title> 9 </head> 10 <body style="text-algin:center"> 11 <h1 style="color:blue">客戶端2的登錄界面</h1> 12 <form action=//localhost:8080/SSOServer/user/login method="post"> 13 <input type="text" name="userName">賬號 14 </br> 15 <input type="password" name="userPassword">密碼 16 </br> 17 <input type="hidden" name="redirectUrl" value="http://localhost:8080/SSOClient2"> 18 <button type="submit">登錄</button> 19 </form> 20 21 </body> 22 </html>
這樣再配置一個相同的客戶端資源我們就可以做測試了。
當分別啟動兩個客戶端時都會跳到登錄頁面,當我們登錄了一個之后,能一個無需登錄直接進入表示成功。
我測試參考https://blog.csdn.net/qq_31183297/article/details/79419222 大家也可以看看。
