1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > SSO单点登录-基于cookie的单点登录

SSO单点登录-基于cookie的单点登录

时间:2024-01-30 17:45:13

相关推荐

SSO单点登录-基于cookie的单点登录

1.概述

单点登录(Single-Sign-On),简称SSO,它的解释为:在多个应用系统中,只要登陆一次,便可以访问其它相互信任的系统。早期系统由于只有一个服务,因此只需要登录一次,就可以访问系统的其它资源。伴随着业务的发展和用户数量的增加,单系统局限性越来越突出(无法支撑大规模用户、用户数量过多系统卡顿等)。为了增强系统的并发能力和解耦合,进行了系统业务的拆分,系统业务拆分后,为了保护系统之间数据安全性,用户需要登录认证才能进行资源访问。若资源分散在不同的服务上,每访问一次都需要重新登录,这会极大地降低用户体验感。解决上述问题的其中一种思路便是在一个系统登录,系统认证成功并返回一个令牌,用户访问其它系统时也携带该令牌,每个系统校验该令牌即可,若校验通过,便允许访问,反之拒绝访问请求,这便是早期单点登录的设计方案。

1.1 普通系统登录原理

在分析单点登录前,首先讲一下普通系统的登录原理。

如上图所示,当我们在浏览器(Browser)中访问一个应用时,用户需要完成登录认证(输入用户名和密码等),当认证成功后,在服务端(Server,这里CloudApp表示部署在云上的Server)的session里会标记该用户的登陆状态为yes(已登录),同时会往访问的浏览器(Browser)写入一个cookie,这个cookie就是当前该用户登录的一个标识,用户每次访问服务时都会携带该cookie,服务端会根据该cookie找到对应的session,然后判断用户的状态。tomcat部署的服务,默认登录后会返回一个名叫jsessionid的cookie,jsessionid对应的的cookie值就是改用户在服务器中的sessionid,该值具有唯一性。

1.2 单点登录

单点登录分为:同根域单点登录、不同根域下单点登录

同根域下单点登录:一般一个企业只会有一个主域名,通过二级域名区分子系统。举个例子:一个企业中有三个子系统(、、),只要在系统中完成了登录验证,同时便在、中完成了登录。由1.1节可知,当用户在完成后,服务器会返回一个cookie(携带sessionid),用户下次访问时,会携带该cookie值服务器,服务器会根据cookie中的sessionid判断用户状态。由于浏览器是不能跨域的,因此中对应的cookie无法被携带至、。

解决该问题的方案:将coockie的域设置为顶域,即.,这样所有子域的系统都可以访问到顶域的cookie。我们在设置cookie时,只能设置顶域和自己的域,不能设置其他的域。解决了cookie共享问题,还有一个服务端session共享问题,由于是不同的服务,那么session如何共享呢?或许有一些常用的方法:

1.Tomcat集群Session全局复制(当数据量较大时,会影响tomcat性能,不建议);

2.根据请求的IP进行Hash映射到对应的机器上(这就相当于请求的IP一直会访问同一个服务器)【如果服务器宕机了,会丢失了一大部分Session的数据,不建议】

3.把Session数据放在Redis中(使用Redis模拟Session)

不同根域下单点登录:这里举个简单的例子,和是两个不同的网站,浏览器不会把的cookie带到下,因此无法实现cookie共享,这是因为不同的域名之间无法实现cookie共享(基于浏览器的安全机制)。针对这种情况要实现单点登录,需要借鉴CAS(Central Authentication Service)的设计思想。CAS的设计思路主要如下图所示:

上述流程的简单描述为:

1.用户访问某一个系统(假设该系统为App1),App1需要进行登录,用户现在处于未登录状态;

2.此时App1会跳转到CAS服务端(即SSO登录系统),SSO系统判断用户未登录,弹出登陆页面;

3.用户输入用户名、密码等,SSO系统认证成功后,将登录状态写入session中,同时将sessionid写入SSO域下的Cookie中,用户登录成功之后,SSO会生成一个ST(Service Ticket),然后重定向到App1系统(携带ST),App1系统拿到ST后,会从后台请求SSO服务,验证ST是否有效,验证成功后,App1系统将登录状态写入session并设置app域下的Cookie,并返回用户信息。

此时用户已完成登录,当再次访问App1时,会保持登录状态。当用户去访问系统App2时,由于App2未登录,App2同样会去访问SSO系统,流程如下:

1.App2跳转到SSO系统;

2.SSO已经登录且生成了ST,SSO携带ST跳转到App2系统;

3.app2拿到ST,后台访问SSO,验证ST是否有效,验证成功后,App2将登录状态写入session,并在App2域下写入Cookie。 用户访问App2系统,App2系统没有登录,跳转到SSO。

此处需要注意的是:SSO的客户端可以是多个。

2.基于cookie的小案例

2.1 设计思想

本文将基于上述CAS的设计思想,实现一个简单的小案例。案例的需求如下:有一个购物车功能和一个首页功能,这两个功能分别属于两个系统,用户登录之后才能访问首页功能和购物车功能,只需登录一次便可访问两个系统的功能。

这里包含的系统如下图所示:

用户访问流程如下图所示:

将上图的流程进行拆分,得到以下几点:

1.当用户访问购物车系统时(该系统需要登录才能访问),用户未登录访问,于是重定向到SSO登陆系统(携带系统地址作为参数),请求地址及形式大致如下:

?target=;

2.SSO登录系统判断用户未登录,会跳转到登录页面,用户输入账号、密码等信息进行登录,登录成功之后会生成token,并写入到cookie中,保存至浏览器;随后,SSO会重定向至target地址(携带token),重定向地址及形式如下:

?token=xxxxxxx

3.此时购物车系统会携带返回回来的token到SSO系统进行验证,验证成功之后,建立会话,修改用户登陆状态;

4.与此同时,用户想要访问首页系统(),由于用户并未在首页系统登录,于是会重定向到SSO登陆系统,请求的地址及方式如下:

?target=;

5.由于在购物车系统已完成登录,即浏览器与已建立了会话(携带cookie至浏览器),此时浏览器会携带cookie至认证中心,浏览器根据cookie中的信息判断已建立会话,SSO登陆系统会携带token重定向至,重定向地址及形式如下:

?token=xxxxxxx

6.首页系统携带token去SSO登录系统认证,如验证通过,则允许访问,修改用户登录状态。

2.2 部分代码

案例需要映射几个不同的域名来模仿不同的系统,可以使用nginx来进行实现,我这里之间在windows的hosts文件中加了如下几行配置:

127.0.0.1 www.127.0.0.1 www.127.0.0.1 www.127.0.0.1

2.2.1 购物车控制器部分代码

package com.eckey.lab.controller;import com.eckey.lab.entity.User;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.CookieValue;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.client.RestTemplate;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;@Slf4j@Controller@RequestMapping("/cart")public class CartController {@Autowiredprivate RestTemplate restTemplate;private final String USER_LOGIN_ADDR = "http://www.:8081/login/info?token=";@GetMapping()public String toIndex(@CookieValue(required = false, value = "token") Cookie cookie, HttpServletRequest request) {if (cookie != null) {String value = cookie.getValue();if (!StringUtils.isEmpty(value)) {ResponseEntity<User> result = restTemplate.getForEntity(USER_LOGIN_ADDR + value, User.class);User user = result.getBody();request.getSession().setAttribute("user", user);}}return "cart";}}

2.2.2 cart.html页面代码

<!DOCTYPE html><html lang="en" xmlns:th="/1999/xhtml"><head><meta charset="UTF-8"><title>Cart</title></head><body><h1>欢迎来到购物车页面</h1><span><a th:if="${session.user == null}"href="http://www.:8081/view/login?target=http://www.:8082/index">登录</a><a th:unless="${session.user == null}" href="http://www.:8081/login/logout">退出</a></span><p th:unless="${session.user == null}"><span style="color: red;" th:text="${session.user.userName}"></span>已登录</p></body></html>

2.2.3 登录控制器

package com.eckey.lab.controller;import com.alibaba.fastjson.JSON;import com.eckey.lab.entity.User;import com.eckey.lab.utils.LoginUsers;import com.eckey.lab.utils.UserUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import com.eckey.lab.utils.EncryptAndDecryptUtil;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;@Slf4j@Controller@RequestMapping("/login")public class LoginController {@Autowiredprivate UserUtils userUtils;@PostMappingpublic String login(User user, HttpServletRequest request, HttpServletResponse response) throws IOException {HttpSession session = request.getSession();String loginUrl = (String) session.getAttribute("loginUrl");log.info("loginUrl:{}", loginUrl);loginUrl = "http://www.:8082/index";if (userUtils.contain(user)) {log.info("用户登录成功:{}", JSON.toJSONString(user));} else {session.setAttribute("msg", "用户" + user.getUserName() + "登录失败");return "login";}log.info("用户登录信息为:{}", JSON.toJSONString(user));if (user == null) {log.error("用户未填写登录名和密码");return "error";}if (user.getPassword() == null || user.getPassword().equals("")) {log.error("password不能为空!");return "error";}if (user.getUserName() == null || user.getUserName().equals("")) {log.error("username不能为空!");return "error";}session.setAttribute("user", user);//todo 应结合加密代码生成token,此处为了演示,做简单生成策略long currentTimeMillis = System.currentTimeMillis();String token = user.getUserName() + "-" + currentTimeMillis;String encryptStr = EncryptAndDecryptUtil.base64Encrypt(token);Cookie cookie = new Cookie("token", encryptStr);cookie.setDomain("");response.addCookie(cookie);LoginUsers.add(encryptStr, user);response.sendRedirect(loginUrl);return "success";}@GetMapping("/logout")public String loginOut(@CookieValue(required = false, value = "token") Cookie cookie, HttpServletRequest request) {HttpSession session = request.getSession();if (cookie == null) {log.info("退出失败,未登录!");session.setAttribute("msg", "退出失败,未登录");return null;}String token = cookie.getValue();if (StringUtils.isEmpty(token)) {log.info("退出失败,未登录!");} else {boolean remove = LoginUsers.remove(token);if (remove) {log.info("退出成功!");}}return "redirect:" + "/view/login";}@GetMapping("/info")@ResponseBodypublic User getToken(@RequestParam("token") String token) {if (StringUtils.isEmpty(token)) {return null;}User user = LoginUsers.USERS.get(token);return user;}}

2.2.4 login.html

<!DOCTYPE html><html lang="en" xmlns:th="/"><head><meta charset="UTF-8"><title>登陆页面</title></head><body><h1>登陆页面</h1><p style="color: red" th:text="${session.msg}"></p><form method="post" action="/login">用户名:<input name="userName" value="" type="text" id="userName">登陆密码:<input name="password" value="" type="password" id="password"><button type="submit">提交</button></form></body></html>

具体详细代码可见章节6。

3.小结

1.SSO的核心在于登录系统的剥离以及系统之间的登录验证,业务系统拿到token后需要再一次调用SSO完成进一步验证,确保token真实性;

2.CAS的设计思想兼顾了功能与安全性,唯一的缺陷在于需要系统之间多次信息交换验证;

3.也可以考虑将用户登录信息防在redis缓存中,设置过期时间,有请求时不断延长时间,这种方式的缺陷在于当用户数量过于庞大时,缓存压力较大,且当redis宕机时,所有的用户登录信息都会消失,用户全部需要重新登录。

4.参考文献

1./post/6844903845424971783

2./p/a58c559bf0e1

3./sinonqu/1575944

4./p/75edcc05acfd

5.声明

本文章为原创文章,若转载,请声明!码字不易,请尊重知识!参考文章及思路已在章节4中标出,在此表示感谢。

6.案例地址

/Marinc/springboot-demos/tree/master/sso-cookie

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。