分布式系统(2)单点登陆解决方案

一. 简介

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,包含单点登录和单点注销两部分

相比于单系统登录,sso需要一个专门的登录中心,多个系统应用中的登录都在登陆中心实现,sso登录中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,令牌作为参数发送给各个子系统,子系统拿到令牌,可以借此创建局部会话,局部会话登录方式与单系统的方式相同

二. 二级域名实现

三. 多个不同域名实现

对于二级域名的单点登录,我们可以非常方便的通过共享cookie来实现,简单的说,就是增加cookie的作用域,这种方式不涉及跨域,当cookie的作用域设置为顶级域名之后,所有的二级域名都可以访问到身份验证的cookie,在服务器端只要验证了这个cookie就可以实现身份的验证

对于多个域名,这个时候就不能共享cookie了,所以上面的解决方案就会失效。那么,要实现跨域的单点登录该如何做呢?

流程图:

在这里插入图片描述

2.1 登陆系统实现

SSO-Server:主要负责用户登录、注销、为SSO-Client分配token、验证token的工作

LoginController :

@Controller
public class LoginController {
    
    
	// 没有redis的序列化配置,这里使用 StringRedisTemplate
	@Autowired
	private StringRedisTemplate redisTemplate;

	@ResponseBody
	@GetMapping("/getLoginUserInfo")
	public String userInfo(@RequestParam("token") String token){
    
    
		Object s = redisTemplate.opsForValue().get(token);
		return s.toString();
	}

	@GetMapping("/login.html")
	public String loginPage(@RequestParam(value = "url",required = true) String url,
							HttpServletRequest request,
							Model model){
    
    
		Cookie[] cookies = request.getCookies();
		if (null != cookies) {
    
    
			for(Cookie cookie : cookies){
    
    
				// 判断cookie中有没有用户登陆信息
				if (cookie.getName().equals("login_user_info") && null != redisTemplate.opsForValue().get(cookie.getValue())) {
    
    
					return "redirect:" + url + "?token=" + cookie.getValue();
				}
			}
		}
		// 没有登陆就跳到登陆页
		model.addAttribute("url",url);
		return "login";
	}

	@PostMapping("/doLogin")
	public String doLogin(@RequestParam(value = "username",required = true) String username,
						  @RequestParam(value = "password",required = true) String password,
						  @RequestParam(value = "url",required = true) String url,
						  HttpServletResponse response) {
    
    
		// get username and password form dataBase ...
		// 从数据库获取到用户信息 校验,登陆失败就返回登陆页面,在这里就不做判断了

		String token = UUID.randomUUID().toString().replace("-", "");
		response.addCookie(new Cookie("login_user_info", token));
		Map<String, Object> userInfo = new HashMap<>();
		userInfo.put("username",username);
		userInfo.put("password","password");
		userInfo.put("otherInfo","otherInfo");
		redisTemplate.opsForValue().set(token, userInfo.toString(), 60L, TimeUnit.MINUTES);
		return "redirect:" + url + "?token=" + token;
	}
}

login.html:

    <form action="/doLogin" method="post">
      <input type="hidden" name="url" th:value="${url}">
        用户名:<input name="username"><br/>
        密码:<input name="password" type="password">
      <input type="submit" value="登录">
    </form>
2.2 客户端实现
@Controller
public class IndexController {
    
    

    // 没有redis的序列化配置,这里使用 StringRedisTemplate
    @Autowired
    private StringRedisTemplate redisTemplate;

    @RequestMapping(value = {
    
    "index.html","/"})
    public String index(Model model) {
    
    
        model.addAttribute("userInfo",null);
        return "index";
    }
    @RequestMapping("login_callback")
    public String loginCallback(@RequestParam(value = "token",required = true) String token,
                                HttpSession session) {
    
    
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> entity = restTemplate.getForEntity("http://ssoserver.com:2000/getLoginUserInfo?token=" + token, String.class);
        String body = entity.getBody();
        if (!StringUtils.isEmpty(body)) {
    
    
            Map<Object, Object> userInfoMap = new HashMap<>();
            userInfoMap.put("username","haiyang");
            session.setAttribute("userInfo",userInfoMap);
        }
        return "redirect:http://ssoclient1.com:2001";
    }
}

index.html:

<a th:if="${session.userInfo != null}">欢迎:[[${session.userInfo == null ? '' : session.userInfo.username}]]</a>
<a href="http://ssoserver.com:2000/login.html?url=http://ssoclient1.com:2001/login_callback" th:if="${session.userInfo == null}">去登录</a>

猜你喜欢

转载自blog.csdn.net/haiyanghan/article/details/115059360