單點登錄 (用Spring+SpringMVC實現的簡析)


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  
pom

然后是所必要的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();
    }
}
sso controller

和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 }
View Code

然后哦是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 }
userdao

然后是我們幫助類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 }
View Code
 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 }
View Code

在之后要去設置一下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>  
View Code

這樣之后我們的服務器就配置好了,我們可以寫一個簡單的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 }
SSOClientController

一個小的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 }
SessionUtil

然后同樣的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>  
View Code

特別說一下,我們設置下兩個頁面。登錄和資源

 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>
View Code
 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>
View Code

 

這樣再配置一個相同的客戶端資源我們就可以做測試了。

當分別啟動兩個客戶端時都會跳到登錄頁面,當我們登錄了一個之后,能一個無需登錄直接進入表示成功。

我測試參考https://blog.csdn.net/qq_31183297/article/details/79419222 大家也可以看看。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM