一、单点登录

单点登录主要分为两个场景,一个是单个域名内不同应用,一个是多个域名多个应用。

1.1 非跨域:共享Session

image.png
共享Session可谓是实现单点登录最直接、最简单的方式。
将Session存储于Redis上,然后将整个系统的全局Cookie Domain设置于顶级域名上,这样SessionID就能在各个子系统间共享。

1.2 跨域:JSONP

image.png

  1. 当用户未登录子系统时,跳转到用户中心(UC)登录,登录后,UC会将token信息存入到UC域名下的cookies中。--login()方法
  2. UC中有一个鉴权接口来返回已登录用户的token信息。--auth()方法
  3. 子系统通过JSONP跨域请求UC系统的鉴权接口,并将这个token存入到子系统的cookies中。 --JSONP页面
  4. 子系统每次业务请求都带上这个cookies。 --hello()方法

中户中心Controller

package cn.wzy.uc.controller;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@RestController
public class UserController {

	private static ConcurrentHashMap<String, String> tokens = new ConcurrentHashMap<>();

	private final String[] referers = new String[]{
		"http://127.0.0.1",
		"http://localhost",
	};

	@Resource
	private HttpServletRequest request;

	@Resource
	private HttpServletResponse response;


	@GetMapping("/login")
	public String login(String username, String password) {
		if (username == null)
			return "登录失败";
		String token = generatorUID();
		response.addCookie(new Cookie("x-token", token));
		tokens.putIfAbsent(token, username);
		return "hello:" + username;
	}


	/**
	 * 返回token信息,用于跨域的token传递
	 * 检验跨域的白名单列表,防止json劫持
	 *
	 * @param callback
	 * @return
	 */
	@GetMapping("/auth")
	public String auth(String callback) {
		System.out.println(callback);
		if (notAllowAuth()) {
			return "禁止该网点单点登录";
		}
		String token = getToken(request.getCookies());
		if (token == null) {
			return "用户未登录";
		}
		String username = tokens.get(token);
		if (username == null) {
			return "用户未登录";
		}
		return callback + "('" + username + "','" + token + "')";
	}

	/**
	 * 是否禁止该网点跨域
	 *
	 * @return false为不禁止
	 */
	private boolean notAllowAuth() {
		String refer = request.getHeader("referer");
		if (refer != null) {
			boolean failed = true;
			for (String ref : referers) {
				if (refer.startsWith(ref)) {
					failed = false;
				}
			}
			return failed;
		}
		return false;
	}

	/**
	 * 通过token鉴权
	 *
	 * @param token
	 * @return
	 */
	@GetMapping("/authForToken")
	public String authForToken(String token) {
		String username = tokens.get(token);
		if (username == null) {
			return "用户未登录";
		}
		return username;
	}


	/**
	 * 生成唯一token
	 *
	 * @return
	 */
	private String generatorUID() {
		return UUID.randomUUID().toString().replaceAll("-", "");
	}

	/**
	 * 获取token字段信息
	 *
	 * @param cookies
	 * @return
	 */
	private String getToken(Cookie[] cookies) {
		if (cookies == null) {
			return null;
		} else {
			for (Cookie cookie : cookies) {
				if (cookie.getName().equals("x-token")) {
					return cookie.getValue();
				}
			}
			return null;
		}
	}
}

子系统JSONP页面

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>单点登录</title>
    <script src="http://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<script>
    function call(username, token) {
        console.log(username);
        console.log(token);
        document.cookie="x-token="+token;
    }
</script>
<script type="text/javascript" src="http://localhost:8080/uc/auth?callback=call"></script>
</body>
</html>

子系统业务Controller

package cn.wzy.sys.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

@RestController
public class UserController {


	@Autowired
	private RestTemplate template;

	@Resource
	private HttpServletRequest request;

	@GetMapping("/hello")
	public String hello() {
		Cookie[] cookies = request.getCookies();
		if (cookies == null) {
			return "用户未登录";
		}
		String token = null;
		for (Cookie cookie : cookies) {
			if (cookie.getName().equals("x-token")) {
				token = cookie.getValue();
			}
		}
		ResponseEntity<String> username = template.getForEntity("http://localhost:8080/uc/authForToken?token=" + token, String.class);
		String str = username.getBody();
		if (str == null || str.equals("用户未登录")) {
			return "用户未登录";
		}
		return "hello:" + str;
	}


}

二、集群的概念

2.1 负载均衡集群

image.png

  1. 负责均衡(Load Balance)服务器根据负载均衡算法(轮询,随机,哈希,权重等)来分发请求到不同的主服务器。
  2. 每个主服务器都是等价的,都可以完成相同的功能
  3. 容错(fall-over)是负载均衡服务器里面的一个概念。是指当一台主服务器宕机后,集群能够继续提供服务的策略。比如说当主服务器A宕机后,负载均衡服务器要能发现主服务器A不能继续提供服务了,以前分发到主服务器A的请求要分发到其它主服务器。这种处理就是容错处理。

2.2 分布式集群

image.png
分布式集群的各个服务器是用来完成不同系统功能的,通过各个服务器一起工作来完成系统各个功能。

2.3 高可用集群

image.png

  1. 高可用的含义就是当一台服务器宕机后,服务可以继续使用以及数据不会丢失。
  2. 如果是负载均衡集群,当负载均衡服务器宕机后,整个服务就不可以使用了。
  3. 如果是主服务器A宕机后,即使原本分发到主服务器A的请求可以重新分发到主服务器B,主服务器A上的缓存数据也会丢失,所以说只用负载均衡集群无法提供高可用。
  4. 实现高可用的思想很简单,就是采用主从(master->slave)备份。从服务器为主服务器的备份,当主服务器宕机后,根据一定算法从所有从服务器中再挑选出一台服务器来作为主服务器继续提供服务。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议