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 大家也可以看看。