手写SSO单点登录

官方介绍:单点登录,简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。

一,单点登录实现的原理

以单个客户端访问单个服务端来讲,以下图为例!

在这里插入图片描述

1,会话机制

1.浏览器在第一次访问Tomcat服务器的时候,Tomcat服务器会在服务端创建session对象,并存储到map中,keysessionidvalue是session对象本身。

2.在响应的时候会把sessionid通过cookie的方式写到客户端浏览器中。

3.浏览器会在本地的目录中把sessionid写入到本地的cookie中。

4.在后续的请求中,都会读取本地的cookie中的内容,并在请求的时候带上对应的cookie

在这里插入图片描述

有了会话机制,登录状态就好明白了,我们假设浏览器第一次请求服务器需要输入用户名与密码验证身份,服务器拿到用户名密码去数据库比对,正确的话说明当前持有这个会话的用户是合法用户,应该将这个会话标记为“已授权”或者“已登录”等等之类的状态,既然是会话的状态,自然要保存在会话对象中,Tomcat在会话对象中设置登录状态如下:

HttpSession session = request.getSession();
session.setAttribute("isLogin", true);

用户再次访问时,tomcat在会话对象中查看登录状态:

HttpSession session = request.getSession();
session.getAttribute("isLogin");

实现了登录状态的浏览器请求服务器模型如下图描述

在这里插入图片描述

每次请求受保护资源时都会检查会话对象中的登录状态,只有isLogin=true 的会话才能访问,登录机制因此而实现。

2,多系统的复杂性

随着公司的发展,公司内部使用的系统越来越多,但是对于使用系统的员工来说不是个好事情。

 1.每个系统都需要记住对应的账号和密码,很多员工都是每个系统的账户和密码都一样的。

 2.如果同时要使用CRM系统,WMS系统,OA系统,用户需要登录三次。

 3.如果不使用了,还需要分别在三个系统中依次的注销。

有没有这样的功能:我只需要登录一次,公司里面所有的系统都可以使用,只需要注销一次,所有的系统都退出登录了,

如果能实现这样的功能就非常好了!

单系统登录解决方案的核心是cookiecookie携带会话id在浏览器与服务器之间维护会话状态。但cookie是有限制的,这个限制就是cookie的域(通常对应网站的域名),浏览器发送http请求时会自动携带与该域匹配的cookie,而不是所有cookie什么意思呢?就是每个域名下面都有属于自己的cookie域,如果是多个系统,那么就会有多个域名,而这些下面的cookie不能实现共享,只能各玩各的。

在这里插入图片描述

既然这样,为什么不将web应用群中所有子系统的域名统一在一个顶级域名下,例如*.baidu.com,然后将它们的cookie域设置为baidu.com,这种做法理论上是可以的,甚至早期很多多系统登录就采用这种同域名共享cookie的方式。

然而,可行并不代表好,共享cookie的方式存在众多局限。首先,应用群域名得统一;其次,应用群各系统使用的技术(至少是web服务器)要相同,不然cookiekey值(tomcatJSESSIONID)不同,无法维持会话,共享cookie的方式是无法实现跨语言技术平台登录的,比如javaphp.net系统之间;第三,cookie本身不安全。

因此,我们需要一种全新的登录方式来实现多系统应用群的登录,这就是单点登录!

3,单点登录的实现过程

相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,用下图说明。

在这里插入图片描述

4,单点注销的实现过程

单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明。

在这里插入图片描述

二,Http间的通信

sso认证中心与sso客户端通信方式有多种,这里以简单好用的HttpURLConnection为例,webServicerpcrestful api都可以。

1,HttpUrlConnection的简单实用

JDKjava.net包中已经提供了访问HTTP协议的基本功能的类:HttpURLConnection

HttpURLConnectionJava的标准类,它继承自URLConnection,可用于向指定网站发送GET请求、POST请求。

具体代码在下面的代码实现步骤再详细讲解!

三,单点登录的实现步骤(语言描述)

1,客户端操作

1.拦截客户端的请求判断是否有局部的session

2.1如果有局部的session,放行请求

2.2如果没有局部session

   2.2.1请求中有携带token参数
    
             2.2.1.1如果有,使用HttpURLConnection发送请求校验token是否有效
    
                           2.2.1.1.1如果token有效,建立局部的session

2.2.1.1.2如果token无效,重定向到统一认证中心页面进行登陆

             2.2.1.2如果没有,重定向到统一认证中心页面进行登陆

  2.2.2请求中没有携带token参数,重定向到统一认证中心页面进行登陆

2,服务端操作

1.检测客户端在服务端是否已经登录了(checkLogin方法)

1.1获取session中的token

1.2如果token不为空,说明服务端已经登录过了,此时重定向到客户端的地址,并把token带上

1.3如果token为空,跳转到统一认证中心的的登录页面,并把redirectUrl放入到request域中

2.统一认证中心的登录方法(login方法)

2.1判断用户提交的账号密码是否正确

2.2如果正确

  	  2.2.1创建token(可以使用UUID,保证唯一就可以)

  	  2.2.2把token放入到session中

		2.2.3这个token要知道有哪些客户端登陆了,创建Map<String,List<String[]> clientMap;(为单点注销做准备)

   SSOUtil.clientMap.put(token,new ArrayList());(把这些数据放入到数据库中也是可以的,我们就做比较简单的,模拟一下)

  	 2.2.4转发到redirectUrl地址,把token带上

2.3如果错误

转发到login.jsp,还需要把redirectUrl参数放入到request域中

3.统一认证中心认证token方法(verify方法),返回值为String,贴@ResponseBody

3.1如果SSOUtil.clientMap.get(token)有数据clientList,说明token是有效的

  	  3.1.1clientList把客户端传入的客户端登出地址(clientLogOutUrl)和会话ID(jsessionid)保存到集合中

  	  3.1.2返回true字符串

3.1如果SSOUtil.clientMap.get(token)为null,说明token是无效的,返回false字符串

四,单点注销的实现步骤(语言描述)

1,客户端操作

在登陆的按钮链接写上统一认证中心的登出方法即可.

  <a href="http://www.sso.com/logOut">退出</a>

2,服务端操作

1,编写logOut方法,调用session.invalidate()

2,创建session的监听器,在session的监听器的销毁方法写如下逻辑

    2.1,获取session中的token
  
    2.2,根据token在SSOUtil.clientMap获取所有客户端的登出地址和会话id
  
    2.3,通过HttpUtil选项调用客户端的登出方法

3,将session监听器注册到web.xml

五,手写单点登录的实现

1,在hosts文件中添加配置

127.0.0.1 www.zhongxin.com
127.0.0.1 www.jiudian.com
127.0.0.1 www.wuliu.com

这样做的目的是测试的时候可以明确的分辨出来每个系统。

2,创建三个webspringboot项目

  • sso_client1 酒店管理系统
  • sso_client2物流管理系统
  • sso_server统一认证中心

因为是在本机上测试,所以给酒店管理系统设置端口为8081,物流管理系统端口为8082,而统一认证中心的端口为8080

这样的话,访问每个系统的路径就是

(1)子系统的代码实现

首先写第一个子系统(酒店管理系统),其实子系统的功能代码都是一样的,唯一不一样的无非就是改一下端口这些最主要的区别,一个子系统写好之后复制一份就是另一个子系统了。

配置sso.properties文件

这个配置文件的作用其实就是指定了统一认证中心的地址和本系统地址,为了后续到这个文件中直接取地址,而不用手写了。

server-url-prefix=http://www.zhongxin.com:8080
client-host-url=http://www.jiudian.com:8081

添加SSOClientUtil工具类

这个工具类就是去sso.properties配置文件中拿地址,并且封装了跳转到统一认证中心的方法。

public class SSOClientUtil {
	private static Properties ssoProperties = new Properties();
	public static String SERVER_URL_PREFIX;//统一认证中心地址:http://www.sso.com:8443,在sso.properties配置
	public static String CLIENT_HOST_URL;//当前客户端地址:http://www.crm.com:8088,在sso.properties配置
	static{
		try {
	ssoProperties.load(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		SERVER_URL_PREFIX = ssoProperties.getProperty("server-url-prefix");
		CLIENT_HOST_URL = ssoProperties.getProperty("client-host-url");
	}
	/**
	 * 当客户端请求被拦截,跳往统一认证中心,需要带redirectUrl的参数,统一认证中心登录后回调的地址
	 * 通过Request获取这次请求的地址 http://www.jiudian.com:8081/
	 * 
	 * @param request
	 * @return
	 */
	public static String getRedirectUrl(HttpServletRequest request){
		//获取请求URL
		return CLIENT_HOST_URL+request.getServletPath();
	}
	/**
	 * 根据request获取跳转到统一认证中心的地址 http://www.zhongxin.com:8080/checkLogin?redirectUrl=http://www.jiudian.com:8081/
	 * 通过Response跳转到指定的地址
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	public static void redirectToSSOURL(HttpServletRequest request,HttpServletResponse response) throws IOException {
		String redirectUrl = getRedirectUrl(request);
		StringBuilder url = new StringBuilder(50)
				.append(SERVER_URL_PREFIX)
				.append("/checkLogin?redirectUrl=")
				.append(redirectUrl);
		//转发到注册中心
		response.sendRedirect(url.toString());
	}
	/**
	 * 获取客户端的完整登出地址 http://www.jiudian.com:8081/logOut
	 * @return
	 */
	public static String getClientLogOutUrl(){
		return CLIENT_HOST_URL+"/logOut";
	}
	/**
	 * 获取认证中心的登出地址 http://www.zhongxin.com:8080/logOut
	 * @return
	 */
	public static String getServerLogOutUrl(){
		return SERVER_URL_PREFIX+"/logOut";
	}
}

添加HttpUtil工具类

这个工具类的作用主要是帮我们封装了发起浏览器的请求,跨域访问,参数使用Map集合进行传递。

public class HttpUtil {
	/**
	 * 模拟浏览器的请求
	 * @param httpURL 发送请求的地址
	 * @param params  请求参数
	 * @return
	 * @throws Exception
	 */
	public static String sendHttpRequest(String httpURL,Map<String,String> params) throws Exception{
		//建立URL连接对象
		URL url = new URL(httpURL);
		//创建连接
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		//设置请求的方式(需要是大写的)
		conn.setRequestMethod("POST");
		//设置需要输出
		conn.setDoOutput(true);
		//判断是否有参数.
		if(params!=null&&params.size()>0){
			StringBuilder sb = new StringBuilder();
			for(Entry<String,String> entry:params.entrySet()){
				sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
			}
			//sb.substring(1)去除最前面的&
			conn.getOutputStream().write(sb.substring(1).toString().getBytes("utf-8"));
		}
		//发送请求到服务器
		conn.connect();
		//获取远程响应的内容.
		String responseContent = StreamUtils.copyToString(conn.getInputStream(),Charset.forName("utf-8"));
		conn.disconnect();
		return responseContent;
	}
	/**
	 * 模拟浏览器的请求
	 * @param httpURL 发送请求的地址
	 * @param jesssionId 会话Id
	 * @return
	 * @throws Exception
	 */
	public static void sendHttpRequest(String httpURL,String jesssionId) throws Exception{
		//建立URL连接对象
		URL url = new URL(httpURL);
		//创建连接
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		//设置请求的方式(需要是大写的)
		conn.setRequestMethod("POST");
		//设置需要输出
		conn.setDoOutput(true);
		conn.addRequestProperty("Cookie","JSESSIONID="+jesssionId);
		//发送请求到服务器
		conn.connect();
		conn.getInputStream();
		conn.disconnect();
	}
}

准备html页面和controller跳转方法

这一切都是在配置了thymeleaf模板引擎的前提下进行的。

就是在浏览器发起请求http://www.jiudian.com:8081/main可以跳到欢迎页面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
欢迎进入一天一次酒店的管理系统
<a href="http://www.zhongxin.com:8080/logout">退出</a>
</body>
</html>
@Controller
public class TestController {
    @RequestMapping("/main")
    public String show(){
        return "index";
    }
}

配置过滤器

当然,在没有登录的时候是不可以直接访问欢迎页面的,这里说明一下:登录是在统一认证中心进行的!所以需要配置一个过滤器,在发起请求之后会先进到过滤器中。

在过滤器中一共做下面几件事情:

  1. 判断是否有局部会话,如果有,则放行,如果没有,重定向到统一认证中心,检查是否有其他的系统已经登录过。
  2. 判断地址栏中是否携带了token参数(这是在统一认证中心登录之后重定向过来的请求,会带上token),如果有token,那么向统一认证中心发起请求来判断该token是否由统一认证中心产生的,如果是,则放行,如果不是,重定向到统一认证中心去登录。

说明:

  • 通过实现Filter接口,创建一个过滤器类,通过@WebFilter注解,注册过滤器。
  • urlPatterns属性代表需要被过滤的请求地址。filterName属性代表过滤器名称;
  • SpringBoot应用启动类中,添加@ServletComponentScan注解,表示项目启动自动扫描Servlet组件。Filter属于Servlet组件

引起注意的一点:重定向到统一认证中心的路径是http://www.zhongxin.com:8080/checkLogin?redirectUrl=http://www.jiudian.com:8081/main这种格式的,就是前面是http请求,后面加上了redirectUrl参数,为本子系统的访问路径,因为把这个传到认证中心之后,在认证中心登录之后还要拿到这个参数(子系统访问路径)进行转发回来。

看一下底层代码

在这里插入图片描述

在这里插入图片描述

它是将认证中心的路径和子系统的路径做了一个拼接,然后response进行了重定向。

过滤器代码

/**
 * 通过实现Filter接口,创建一个过滤器类
 * 通过@WebFilter注解,注册过滤器。urlPatterns属性代表需要被过滤的请求地址。filterName属性代表过滤器名称
 * 在SpringBoot应用启动类中,添加@ServletComponentScan注解,表示项目启动自动扫描Servlet组件。Filter属于Servlet组件
 */
@Component  //加入tomcat容器
@WebFilter(urlPatterns = "/*",filterName = "ssoFilter")
public class SSOClientFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.err.println("############进入了过滤器#############");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res= (HttpServletResponse) response;
        HttpSession session = req.getSession();
        //1,判断是否有局部的会话
        Boolean isLogin= (Boolean) session.getAttribute("isLogin");
        if(isLogin!=null && isLogin){
            //有局部会话,直接放行
            chain.doFilter(request,response);
            return;
        }
        //判断地址栏中是否携带了token参数(这是在统一认证中心登录之后重定向过来的请求,会带上token)
        String token=req.getParameter("token");
        if(token!=null || token !=""){
            //token信息不为null,拥有令牌
            //判断token信息是否由认证中心产生的
            //这里使用写好的工具类发送http请求  SSOClientUtil.SERVER_URL_PREFIX是统一认证中心的IP
            String httpUrl=SSOClientUtil.SERVER_URL_PREFIX+"/verify";
            //参数
            Map<String,String> params=new HashMap<String, String>();
            params.put("token",token);
            //这里放入clientUrl和jsessionId这两个参数是为了单点注销做准备
            params.put("clientUrl",SSOClientUtil.getClientLogOutUrl());
            params.put("jsessionId",session.getId());
            try {
                //发送请求,server端(统一认证中心)会返回一个字符串
                String isVerify = HttpUtil.sendHttpRequest(httpUrl, params);
                if("true".equals(isVerify)){
                    //如果返回的字符串是true,说明这个token是由认证中心产生的
                    //创建局部的会话
                    session.setAttribute("isLogin",true);
                    //放行该次请求
                    chain.doFilter(request,response);
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //2,没有局部会话,重定向到统一认证中心,检查是否有其他的系统已经登录过
        //http://www.zhongxin.com:8080/checkLogin?redirectUrl=http://www.jiudian.com:8081
        SSOClientUtil.redirectToSSOURL(req,res);
    }
    @Override
    public void destroy() {
    }
}

到这里子系统的代码已经完成了,接下来开始写统一认证中心。

(2)统一认证中心的代码实现

首先项目要配置好thymeleaf模板引擎,这都不用说了吧?

然后先准备一个登录页面。表单项redirectUrl是隐藏的,是子系统的路径,是表单提交的一个参数,因为在登录方法中进行验证,如果账号密码成功之后,还要拿到这个路径进行重定向回来。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <script src="js/jquery.1.12.4.min.js"></script>
</head>
<body>
<h1>请登录</h1>
<!--<span id="span" th:text="${redirectUrl}"></span>-->
<form id="form" action="/login" method="post">
    <input type="hidden" th:value="${redirectUrl}" name="redirectUrl">
    账号:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br><input type="submit" value="登录">
</form>
</body>
</html>

模拟数据库

这里没有连接数据库,只是使用集合来存储数据来模拟一下。

其实需要两张表,一张是存放token令牌的表T_TOKEN,另一张是存放统计注册过的子系统信息的表T_CLIENT_INFO(token为id,其他字段为clientUrl(子系统访问路径),jsessionid(session的id),这里使用集合表示了)

先建一个实体类,装载子系统的信息。

public class ClientInfo {
    private String clientUrl;
    private String jsessionid;
    }

DataBaseUtil类,连个集合(代表两张表)

public class DataBaseUtil {
    //创建一个存放token令牌信息的集合
    public static Set<String> T_TOKEN=new HashSet<String>();
    //创建一个统计注册过的子系统信息的集合
    public static Map<String, List<ClientInfo>> T_CLIENT_INFO=new HashMap<String, List<ClientInfo>>();
}

添加HttpUtil工具类

因为如果登录成功之后要重定向到子系统,所以需要这个工具类来进行跨域请求。

这个类上面已经给过了,略过。

在controller进行处理(中心内容)

在这个controller中要做下面几件事:

  1. 检查是否登录,判断是否有全局会话,如果没有,跳转到统一认证中心的登录页面,也就是本系统的登录页面,如果有,

取出令牌信息,重定向到子系统,把令牌带上。

 2. 登录功能,检查账号密码是否匹配, 如果匹配,创建令牌信息,创建全局会话,把令牌信息放到会话中,重定向`redirectUrl`子系统,并把令牌信息带上;如果不匹配,则重新回到登录页面,还需要把`redirectUrl`放到`request`域中。
    3. 校验`token`是否由统一认证中心产生的,如果是,则返回`true`字符串,并记录客户端的登出地址,记录登出地址是为了单点注销做准备的。
    4. 单点注销。
@Controller
public class TestController {
    @RequestMapping("/checkLogin")
    public String show(String redirectUrl, HttpSession session, Model model){
        //1,判断是否有全局的会话
        String token= (String) session.getAttribute("token");
        if(token==null || token==""){
            //表示没有全局会话
            //跳转到统一认证中心的登录页面,也就是本系统的登录页面
            model.addAttribute("redirectUrl",redirectUrl);
            return "login";
        }
        else{
            //有全局会话
            //取出令牌信息,重定向到redirectUrl,把令牌带上
            return "redirect:"+redirectUrl+"?token="+token;
        }
    }
    /**
     * 登录功能
     */
    @RequestMapping("/login")
    public String login(String username,String password,String redirectUrl,HttpSession session,Model model){
        if("admin".equals(username)&&"123".equals(password)){
            //账号密码匹配
            //1,创建令牌信息
            String token= UUID.randomUUID().toString();
            //2,创建全局会话,把令牌信息放到会话中
            session.setAttribute("token",token);
            //3,需要把令牌放到数据库中,这里不连数据库,创建list模拟一下
            DataBaseUtil.T_TOKEN.add(token);
            //4,重定向redirectUrl,并把令牌信息带上 http://www.jiudian.com:8081/main?token=
            //参数可存放到model,可以直接重定向带过去
            return "redirect:"+redirectUrl+"?token="+token;
        }else{
            //账号密码错误,重新回到登录页面,还需要把redirectUrl放到request域中
            model.addAttribute("redirectUrl",redirectUrl);
            return "login";
        }

    }
    /**
     * 校验token是否由统一认证中心产生的
     */
    @RequestMapping("/verify")
    @ResponseBody
    public String verify(String token,String clientUrl,String jsessionId){
    if(DataBaseUtil.T_TOKEN.contains(token)){
        //记录客户端的登出地址
        List<ClientInfo> clientInfoList = DataBaseUtil.T_CLIENT_INFO.get("token");
        if(clientInfoList==null){
            clientInfoList=new ArrayList<ClientInfo>();
            DataBaseUtil.T_CLIENT_INFO.put(token,clientInfoList);
        }
        ClientInfo clientInfo=new ClientInfo();
        clientInfo.setClientUrl(clientUrl);
        clientInfo.setJsessionid(jsessionId);
        clientInfoList.add(clientInfo);

        //说明令牌有效,返回true
        return "true";
    }
    return "false";
    }
    
    /**
     * 单点注销
     */
    @RequestMapping("logout")
    @ResponseBody
    public String logout(HttpSession session){
        /**
         * 销毁全局会话
         * session.invalidate()方法会执行session监听器的销毁方法(什么是session监听器?)
         */
        session.invalidate();
        return "退出成功!";
    }
}

单点注销

单点注销其实就是在子系统的退出按钮上添加路径为http://www.zhongxin.com:8080/logout即可,然后在统一认证中心进行销毁全局会话的操作。

通过 session.invalidate()进行销毁全局会话,该方法会执行session监听器的销毁方法。至于什么是监听器,上网查吧!

创建一个session会话监听器

该监听类继承HttpSessionListener实现两个方法,sessionDestroyed方法是sesison在销毁的时候执行的动作。

springboot项目加入session监听器和过滤器,都要加上@WebListener注解,在启动类上都要加上@ServletComponentScan(com.aaa)注解。

//session监听
@WebListener  //加到tomcat容器中  springboot项目加入session监听器和过滤器,都要加上@WebListener注解,在启动类上都要加上@ServletComponentScan注解
public class MySessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {

    }

    /**
     * session销毁监听
     * @param se
     */
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        String token= (String) session.getAttribute("token");
        //删除token表中的数据
        DataBaseUtil.T_TOKEN.remove(token);
        List<ClientInfo> clientInfoList = DataBaseUtil.T_CLIENT_INFO.remove(token);
            try {
                //获取出注册的子系统,依次调用子系统登出的方法,也就是把子系统的session全部清除掉
                for(ClientInfo clientInfo:clientInfoList) {
                    HttpUtil.sendHttpRequest(clientInfo.getClientUrl(), clientInfo.getJsessionid());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
}

然后单点登录就搞定了。

补充:其实上面的单点注销并没有实现,不清楚哪里除了问题,不过后来通过一个简单的方法实现了,就是在session监听销毁中的HttpUtil.sendHttpRequesturl路径参数的进行字符串拼接然后改成了http:www.jiudian.com:8081/go这样的路径,然后分别在子系统的controller中添加go的映射方法,把sessionisLogin设为false即可。虽然有点儿low,但是实现了!

六,手写单点登录的效果展示

首先把三个项目都启动起来。

然后先访问酒店管理系统,在浏览器发起请求http://www.jiudian.com:8081/main

然后会跳转到统一认证中心的登录页面

在这里插入图片描述

然后访问物流管理系统,在浏览器发起请求http://www.wuliu.com:8082/main

它还是会跳转到统一认证中心的登录页面

然后,重点来了,在酒店管理系统的登录下输入账号密码,进行登录

在这里插入图片描述

如果错误会重新返回登录页面,这里登录成功

然后会重定向到子系统酒店的欢迎页面

在这里插入图片描述

此时再去访问物流管理系统时,你会发现不用登录就直接进到物流管理系统的欢迎页了!

在这里插入图片描述

单点注销就不演示了,一个子系统进行退出之后,在统一认证中心销毁了全局会话信息,然后所有子系统都退出了!

七,知识点

1,@Slf4j日志

该日志是属于lombok插件的,安装lombok插件,或者在maven中并导入lombok依赖。

		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
            <scope>provided</scope>
        </dependency>

然后就可以使用了!

使用方法:在类上面加上注解@Slf4j,然后通过在代码中log.info()来打印日志。

2,springboot配置session监听器

session监听器由session对象调用invalidate()方法而调用,session监听器监听session的创建和销毁。

使用步骤:

在需要销毁session的地方写

session.invalidate();

然后就会调用session监听器的销毁方法,如果一个客户端一登录进来记录了session,那么会第一个就是进入session监听器的创建方法。

session监听器如何配置?

springboot项目加入session监听器和过滤器,都要加上@WebListener注解,在启动类上都要加上@ServletComponentScan(com.aaa)注解。

@ServletComponentScan注解一定要加包名进行扫描,否则扫描不到该监听器,具体扫描机制待以后研究!(都是经验,前辈趟过的路就不要走第二次了)

@WebListener  
public class MySessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) { 
    }
}

3,重定向发起http请求的方式

  • response.sendRedirect重定向
  • return "redirect"重定向

response重定向的方式,举个例子

public void redirectToSSOURL(HttpServletRequest request,HttpServletResponse response) {
		String redirectUrl = getRedirectUrl(request);//这句代码忽略,只是举个例子而已,不要管这个参数
		StringBuilder url = new StringBuilder(50)
				.append("http://www.zhongxin.com:8080")
				.append("/checkLogin?redirectUrl=")
				.append(redirectUrl);
		//转发到注册中心
		response.sendRedirect(url.toString());
	}

这样重定向的路径就是http://www.zhongxin.com:8080/checkLogin?redirectUrl=XXX

return "redirect"重定向方式,再举个例子

 @RequestMapping("/checkLogin")
    public String show(){
      		String redirectUrl="http://www.jiudian.com:8081";
        	String token="test"
            return "redirect:"+redirectUrl+"?token="+token;
        }
    }

这样重定向的路径就是http://www.jiudian.com:8081?token=test

注意:只有重定向才能跨域发起请求,而转发只能在本系统进行转发,不能跨域。

八,结束语

真正企业级的单点登录肯定不会像上面那样实现,上面只是手写实现了单点登录,真正给企业部署单点登录的话就需要很多复杂性的内容了,而且还会考虑到更多安全性的问题。

单点登录结束!

posted @ 2020-03-08 17:24  你樊不樊  阅读(592)  评论(0编辑  收藏  举报