【分布式登录 002】单点登录SSO(好文001)

    科技2024-03-25  12

    文章目录

    一、前言二、分布式架构导致了单点登录三、单体架构的登录3.1 单体架构登录理论3.2 单体架构登录实现思路(三个函数)3.3 Demo:单体架构的登录(三个函数) 四、多系统登录的问题与解决4.1 问题1:Session不共享问题4.2 问题2:Cookie跨域的问题4.3 Demo:分布式架构的单点登录(三个函数) 五、CAS Center Authentication Service 认证中心服务(即SSO微服务)六、面试金手指(要记的东西)6.1 分布式架构导致了单点登录 + session和cookie两个不同6.2 单体架构的登录6.2.1 单体架构登录理论6.2.2 单体架构登录实现思路(三个函数)6.2.3 Demo:单体架构的登录(三个函数)(重点001) 6.3 多系统登录的问题与解决6.3.1 问题1:Session不共享问题(重点002)6.3.2 问题2:Cookie跨域的问题(重点003)6.3.3 Demo:分布式架构的单点登录(三个函数)(重点004) 6.4 CAS原理(认证中心服务) 七、小结

    一、前言

    单点登录定义:英文名全称 Single Sign On(简称SSO),译为单点登录。

    二、分布式架构导致了单点登录

    黄金一句:分布式架构导致了单点登录,单点登录就是分布式登录,就是分布式系统中,一个系统登录,其他所有系统共享登录状态。

    单体架构中,所有的功能都在同一个系统上。 后来,我们为了合理利用资源和降低耦合性,于是把单系统拆分成多个子系统,形成了分布式架构。 实践:阿里系的淘宝和天猫,很明显地我们可以知道这是两个系统,但是你在使用的时候,登录了天猫,淘宝也会自动登录。所以,单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。

    三、单体架构的登录

    3.1 单体架构登录理论

    单体架构登录理论

    HTTP是无状态的协议,这意味着服务器无法确认用户的信息。于是乎,W3C就提出了:给每一个用户都发一个通行证,无论谁访问的时候都需要携带通行证,这样服务器就可以从通行证上确认用户的信息。通行证就是Cookie。

    如果说Cookie是检查用户身上的”通行证“来确认用户的身份,那么Session就是通过检查服务器上的”客户明细表“来确认用户的身份的。Session相当于在服务器中建立了一份“客户明细表”。

    HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一个用户。于是乎:服务器向用户浏览器发送了一个名为JESSIONID的Cookie,它的值是Session的id值。其实Session是依据Cookie来识别是否是同一个用户。金手指:Session是存放在Cookie里面的,当然也可以存放在URL里面。

    3.2 单体架构登录实现思路(三个函数)

    单体架构登录实现思路: 第一,实现登录功能:服务端中,将用户信息保存在Session对象中, 如果在Session对象中能查到,说明已经登录 如果在Session对象中查不到,说明没登录(或者已经退出了登录) 第二,实现注销功能(退出登录):从Session中删除用户的信息 第三,实现记住我功能(关闭掉浏览器后,重新打开浏览器还能保持登录状态):配合Cookie来用(Session默认是)

    金手指:解释三功能(登录、注销、记住我),记住cookie和session重要的点 登录:因为第一次访问,登陆的时候,服务器已经将Cookie发送给浏览器,第二次访问,浏览器就直接带着Cookie到服务器了,所以不用手动登陆了(但是第一次没有存放用户token的Cookie的,必须手动登陆)。 注销:删除服务端内存中的session和浏览器/磁盘中的cookie 记住我:登录后未注销的第 2-n 次登录功能(因为注销会删除内存中的session和浏览器/磁盘中的cookie,所以下一次又要重新发送cookie了)

    3.3 Demo:单体架构的登录(三个函数)

    单体架构的登录Demo

    // 第一,用户登录功能,第一次登录功能,因为第一次访问,登陆的时候,服务器已经将Cookie发送给浏览器,第二次访问,浏览器就直接带着Cookie到服务器了,所以不用手动登陆了(但是第一次没有存放用户token的Cookie的,必须手动登陆)。 @PostMapping(value = "/user/session", produces = {"application/json;charset=UTF-8"}) public Result login(String mobileNo, String password, String inputCaptcha, HttpSession session, HttpServletResponse response) { if (WebUtils.validateCaptcha(inputCaptcha, "captcha", session)) { //判断验证码是否正确 User user = userService.userLogin(mobileNo, password); //根据账号密码,判断有没有该用户 if (user != null) { // 第一步,将token保存在数据库中 //3句:生成与用户对应的token,user设置loginToken属性,将带有logintoken的user对象写入到数据库的user表中去 //注意:这个user表有一个loginToken字段,这个字段cookie里面也有,这就是自动登录一个新奇的原理 String loginToken = WebUtils.md5(new Date().toString() + session.getId()); // 通过当前时间和sesssion得到与用户对应的loginToken user.setLoginToken(loginToken); // user设置loginToken属性 User user1 = userService.userUpload(user); // 第一次登录,将用户带loginToken的user写入数据库中,表示这个用户已经登录 // 第二,设置session和cookie,将cookie设置为一个星期,表示自动登陆持续一个星期 session.setAttribute("user", user1); // user1设置在session里面 CookieUtil.addCookie(response,"loginToken",loginToken,604800); // 将token设置在Cookie里面,设置一个星期 return ResultUtil.success(user1); // 返回指定结构体的成功 } else { return ResultUtil.error(ResultEnum.LOGIN_ERROR); // 返回指定结构体的失败 } } else { return ResultUtil.error(ResultEnum.CAPTCHA_ERROR); // 返回指定结构体的失败 } } // 第二,用户退出功能,注销功能,删除服务端内存中的session和浏览器/磁盘中的cookie @DeleteMapping(value = "/session", produces = {"application/json;charset=UTF-8"}) public Result logout(HttpSession session,HttpServletRequest request,HttpServletResponse response ) { //删除session和cookie,简单 session.removeAttribute("user"); // session中去除掉user属性,发命令让浏览器删去磁盘中的cookie文件 CookieUtil.clearCookie(request, response, "loginToken"); return ResultUtil.success(); } // 第三,拦截器;记住我,实现自动登陆功能,登录后未注销的第 2-n 次登录功能(因为注销会删除内存中的session和浏览器/磁盘中的cookie,所以下一次又要重新发送cookie了) public class UserInterceptor implements HandlerInterceptor { @Autowired private UserService userService; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception { // 第一步,查看服务端session,如果找到,表示登录了,放行,没找到,走else继续 User sessionUser = (User) request.getSession().getAttribute("user"); // ps:session是保存在服务器内存中的,一次会话就没了,就是关闭浏览器页面就没有了,所以session中没有不能代表没有登录,要继续判断 if (sessionUser != null) { return true; } else { //第二步,当服务端指定session为null,查看带过来cookie,是否存在loginToken(第一次登录的时候已经给cookie设置loginToken,并设置一个星期过期时间了) String loginToken = CookieUtil.findCookieByName(request, "loginToken"); if (StringUtils.isNotBlank(loginToken)) { User user = userService.findUserByLoginToken(loginToken); // 第三步,存在loginToken,拿着loginToken到数据库查询有没有该user // ps:数据库表user中有一个列loginToken,loginToken由当前时间+sessionid md5运算生成,是唯一的,用户id也是唯一的,所以loginToken和user是一对一关系,一个loginToken对应一个user,一个user对应一个loginToken if (user != null) { // 用户不为空,找到了 request.getSession().setAttribute("user", user); // 第四步,进入到这里说明没有session,但是有cookie,所以设置好session,然后直接返回为true return true; } else { // 用户为空,找不到 CookieUtil.clearCookie(request, response, "loginToken"); // 第四步,进入到这里说明没有cookie中的loginToken指定的用户,即Cookie不匹配,直接将cookie删掉 返回false return false; } } else { //第三步,session和cookie中都不存在loginToken, if (request.getRequestURI().contains("session")) { // 没有cookie,但是URL里面放着session,放行 return true; } //没有sesssion,没有cookie凭证,要求登录,跳转到登录 response.sendRedirect("/login.html"); return false; } } } }

    注意:登录方法中,将token设置到user中,然后将user存放到数据库和session中,token存放在cookie中返回给客户端,所以一共设置三个:数据库、session、cookie; 但是,在注销接口中,仅仅删除session中的user 和 cookie中的token,删除两个,没有删除数据库中的user记录; 记住我 接口中,先验证session,然后验证cookie,以下情况: 1、session里面可以取到user属性,直接放行(另:url里面有session,放行); 2、session里面无法取到user属性,看cookie里面有没有loginToken 3、比对数据库中的user,如果cookie里面有loginToken且合法(该loginToken可以在数据库里面找到对应user),设置好session,放行。 4、比对数据库中的user,cookie里面有loginToken且不合法(该loginToken可以在数据库里面找到对应user),删除这个cookie,拒绝。 5、没有session,没有cookie,转到登录界面 注意:整个过程中只有cookie中token不合法,删除cookie系列,没有删除数据库中user系列。

    总结一下上面代码的思路: 第一个函数:用户登录时,验证用户的账户和密码(第一次登录功能中的 userService.userLogin(mobileNo, password); ) 第一个函数五行代码:生成一个Token保存在数据库中,将用户数据保存在Session中,并将Token写到Cookie中,这个cookie里面有loginToken,这个cookie保存一星期,表示一个星期自动登录(第一个函数五句代码,前三句是写入数据库,后两句是设置session和cookie) 第二个函数,注销,直接删除session和cookie就好(session中去除掉user属性,发命令让浏览器删去磁盘中的cookie文件) 第三个函数,先验证session,然后验证cookie,以下情况: 1、session里面可以取到user属性,直接放行(另:url里面有session,放行); 2、session里面无法取到user属性,看cookie里面有没有loginToken 3、cookie里面有loginToken且合法(该loginToken可以在数据库里面找到对应user),设置好session,放行。 4、cookie里面有loginToken且不合法(该loginToken可以在数据库里面找到对应user),删除这个cookie,拒绝。 5、没有session,没有cookie,转到登录界面

    四、多系统登录的问题与解决

    4.1 问题1:Session不共享问题

    问题:单系统登录功能主要是用Session保存用户信息来实现的,但我们清楚的是:多系统即可能有多个Tomcat,而Session是依赖当前系统的Tomcat,所以系统A的Session和系统B的Session是不共享的。

    解决系统之间Session不共享问题有一下几种方案: 方案1:Tomcat集群Session全局复制(集群内每个tomcat的session完全同步)【会影响集群的性能呢,不建议】 方案2:根据请求的IP进行Hash映射到对应的机器上(这就相当于请求的IP一直会访问同一个服务器)【如果服务器宕机了,会丢失了一大部分Session的数据,不建议】 方案3:引入一个各个系统都可以访问的中间件Redis,把Session数据放在Redis中,这样所有的系统就都可以知道现在用户登录没有(使用Redis模拟Session)【建议】 共同点(单点登录的核心) 问题:单点登录的问题是session是各个系统所独自拥有的,各个系统不知道用户是否登录,无法共享用户的登录状态, 目标/切入点:目标/切入点是 “一定要让所有的系统就都可以知道现在用户登录没有”,只要能够实现这个目标/切入点,就可以作为方案,所以 Tomcat集群Session全局复制、请求的IP一直会访问同一个服务器、引入中间件Redis 都是方案。

    4.2 问题2:Cookie跨域的问题

    Cookie是不能跨域的 比如说,我们请求< https://www.google.com/>时,浏览器会自动把google.com的Cookie带过去给google的服务器,而不会把< https://www.baidu.com/>的Cookie带过去给google的服务器。 这就意味着,由于域名不同,用户向系统A登录后,系统A返回给浏览器的Cookie,用户再请求系统B的时候不会将系统A的Cookie带过去。

    针对Cookie存在跨域问题,有几种解决方案: 方案1:服务端将Cookie写到客户端后,客户端对Cookie进行解析,将Token解析出来,此后请求都把这个Token带上就行了 方案2:多个域名共享Cookie,具体:服务端在新建Cookie发送给客户端的时候,就设置好Cookie的domain。 方案3:将Token保存在SessionStroage中(不依赖Cookie就没有跨域的问题了) 跨域问题:就是不同域名(一级域名相同二级域名不同)的Cookie,服务端接收不到,但是Cookie不是重点,Cookie里面存放的token才是实现自动登录的重点,token存在于Cookie中,Cookie不能只能让创建cookie的服务端接收到,导致token只能让创建cookie的服务端接收到 切入点/目标:一定要让token在全网服务端都能接收到,知道达到这个目标,都是解决方案 方案1:前端第一个请求服务端,服务端通过Cookie将token给了前端之后,前端将token解析出来,前端 第2-n次请求, 前端就干脆不要再使用Cookie来传输token了,直接将token放到url请求地址后面(笔者第一家公司就是这么做的) 方案2:将Token保存在SessionStroage中(笔者没用过,电商项目中未登录状态下,添加到购物车可以使用sessionStorage或者LocalStorage做,纯前端Vue操作,都是临时数据存储,sessionStorage表示会话内有效,LocalStorage表示指定时间有效) 方案3:前端2-n次请求,还是使用Cookie传输token,但是,第一次请求,服务端在新建Cookie发送给客户端的时候,就设置好Cookie的domain,设置为全网Cookie **小结:token存放在URL、token存放在SessionStorage、干脆设置为顶级Cookie(带token) **

    4.3 Demo:分布式架构的单点登录(三个函数)

    我们可以将登录功能单独抽取出来,做成一个子系统。

    SSO(登录系统)的逻辑如下:

    // 登录功能(SSO系统上的登录接口 三步走,数据库中查找user,核对数据库中user密码,token,user对应关系放入redis) @Override public TaotaoResult login(String username, String password) throws Exception { TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); List<TbUser> list = userMapper.selectByExample(example); // 到数据库中去找参数username,如果没找到,用户不存在,如果找到了,核对密码 if (null == list || list.isEmpty()) { return TaotaoResult.build(400, "用户不存在"); } //第二步,找打了username,核对密码 TbUser user = list.get(0); if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) { return TaotaoResult.build(400, "密码错误"); } //第三步,用户名和密码正确,登录成功,但是不能就这样结束了,一定要放到中间件redis中,让 让所有的系统就都可以知道现在用户登录没有 String token = UUID.randomUUID().toString(); //生成唯一token,用户是唯一的,token也是唯一的,所以user和token 一对一关系 jedisCluster.set(USER_TOKEN_KEY + ":" + token, JsonUtils.objectToJson(user)); // 建立(token,user)的一对一关系,放入到全局中间件redis中 jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME); //设置redis指定key(即USER_TOKEN_KEY + ":" + token)过期时间 return TaotaoResult.ok(token); // 返回指定结构体,返回token,为什么?其他系统的登录接口调用SSO接口,需要得到token,放到cookie里面,将cookie返回给浏览器,存入磁盘文件;第2-n登录其他系统,直接拿着带token的cookie来 }

    其他子系统登录时,请求SSO(登录系统)进行登录,将返回的token写到Cookie中,下次访问时则把Cookie带上:

    // 登录功能(其他系统上的登录接口) public TaotaoResult login(String username, String password, HttpServletRequest request, HttpServletResponse response) { //请求参数 Map<String, String> param = new HashMap<>(); param.put("username", username); param.put("password", password); //第一步,其他系统上的登录接口,直接转到SSO系统的登录接口,交给它去处理就好 String stringResult = HttpClientUtil.doPost(REGISTER_USER_URL + USER_LOGIN_URL, param); TaotaoResult result = TaotaoResult.format(stringResult); //登录出错 if (result.getStatus() != 200) { return result; } //第二步,登录成功后把取token信息,并写入cookie,将cookie返回给浏览器,存入磁盘文件;第2-n登录其他系统,直接拿着带token的cookie来 String token = (String) result.getData(); //写入cookie CookieUtils.setCookie(request, response, "TT_TOKEN", token); //返回成功 return result; } 其他系统的注销功能、其他系统的记住我功能,和上面一样 唯一改变,之前的单系统登录,一个登录接口;现在分布式系统单点登录,变为两个登录接口(SSO系统的登录接口 + 其他业务系统的登录接口)

    对于代码的解释: 1、第一个函数(SSO系统):SSO系统的登录接口,每次生成一个token,建立(token,user)的一对一关系,放入到全局中间件Redis中,并设置过期时间(单机系统的时候,带token的user是设置在session中,等待下次登录用的,这里redis代替session,地位相当于全局session) 2、第一个函数(其他系统):其他系统请求SSO系统进行登录,得到SSO返回的token,写到Cookie中,毕竟每个微服务一个子域Cookie. 3、第三个函数(拦截器):每次请求时,Cookie都会带上,拦截器得到token,判断是否已经登录

    单系统登录到分布式系统的单点登录,本质改变: (token,user)是实现自动登录/记住我的关键, 单体系统中, 第一(同一个session中,保存登录状态,不是重点),服务端存在Session里面(session.setAttribute(“user”, user1);)(第一个函数);下次浏览器登录的时候,服务端内存的session存在(页面没有关闭,是同一个浏览器页面)就直接登录了(request.getSession().getAttribute(“user”); )(第三个函数) 第二((token,user)是实现自动登录的重点),服务端将(token,user)存入数据库( user.setLoginToken(loginToken); User user1 = userService.userUpload(user); ),返回给浏览器带有token的Cookie(第一个函数);下次浏览器直接拿着Cookie里面的token来登录,与数据库(token,user)比对(userService.findUserByLoginToken(loginToken); )(第三个函数) 分布式系统中, 第一个,同一个session中,保存登录状态,没有实现了 第二个,((token,user)是实现自动登录的重点),现在将(token,user)存到Redis,返回给浏览器带有token的Cookie(第一个函数 SSO系统 + 业务系统);下次浏览器直接拿着Cookie里面的token来登录,来redis(token,user)比对(第三个函数) 小结:服务端 (token,user) ,之前存放在session,后来在redis中。

    五、CAS Center Authentication Service 认证中心服务(即SSO微服务)

    说到单点登录,就肯定会见到这个名词:CAS (Central Authentication Service),下面说说CAS是怎么搞的。 如果已经将登录单独抽取成系统出来,我们还能这样玩。

    现在我们有两个系统,分别是www.leyou.com和www.leyou2.com,一个SSOwww.sso.com 第一步,用户想要访问系统A www.leyou.com受限的资源(比如说购物车功能,购物车功能需要登录后才能访问),系统A www.leyou.com发现用户并没有登录,于是重定向到sso认证中心,并将自己的地址作为参数。请求的地址如下: www.sso.com?service=www.leyou.com sso认证中心发现用户未登录,将用户引导至登录页面,用户进行输入用户名和密码进行登录,用户与认证中心建立全局会话(生成一份Token,写到Cookie中,保存在浏览器上)

    第二步,认证中心重定向回系统A,并把Token携带过去给系统A,重定向的地址如下: www.leyou.com?token=xxxxxxx 系统A去sso认证中心验证这个Token是否正确,如果正确,则系统A和用户建立局部会话(创建Session)。到此,系统A和用户已经是登录状态了。

    第三步,用户想要访问系统Bwww.leyou2.com受限的资源(比如说订单功能,订单功能需要登录后才能访问),系统Bwww.leyou2.com发现用户并没有登录,于是重定向到sso认证中心,并将自己的地址作为参数。请求的地址如下: www.sso.com?service=www.leyou2.com 注意,因为之前用户与认证中心www.sso.com已经建立了全局会话(当时已经把Cookie保存到浏览器上了),所以这次系统B重定向到认证中心www.sso.com是可以带上Cookie的。 认证中心根据带过来的Cookie发现已经与用户建立了全局会话了,认证中心重定向回系统B,并把Token携带过去给系统B,重定向的地址如下: www.leyou2.com?token=xxxxxxx 接着,系统B去sso认证中心验证这个Token是否正确,如果正确,则系统B和用户建立局部会话(创建Session)。到此,系统B和用户已经是登录状态了。

    所以,其实SSO认证中心就类似一个中转站。

    六、面试金手指(要记的东西)

    6.1 分布式架构导致了单点登录 + session和cookie两个不同

    黄金一句:分布式架构导致了单点登录,单点登录就是分布式登录,就是分布式系统中,一个系统登录,其他所有系统共享登录状态。 实践:阿里系的淘宝和天猫,很明显地我们可以知道这是两个系统,但是你在使用的时候,登录了天猫,淘宝也会自动登录。所以,单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。

    Session和Cookie的两个不同 1、存储位置不同:Session是存放在服务端的四种作用域之一,Cookie存放在浏览器磁盘文件上。 JSP中的四种作用域包括page、request、session和application,具体来说: page代表与一个页面相关的对象和属性。 request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。request的域对象只能是一次http请求,提交表单数据的时候request域对象的数据取不出来。 session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。 application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。ServletContext代表整个web应用,如果有几个用户浏览器同时访问,ServletContext域对象的数据会被多次覆盖掉,也就是说域对象的数据就毫无意义了。 注意,page request session application/servletContext 都是只有一个的,都是JSP的作用域 2、生命周期不同: Session的生命周期指的是不活动的时间,如果设置session是10s,在10s内,没有访问session,session属性失效,如果在9s的时候,你访问session,就重新计数; 如果重启了tomcat,或者reload web应用,或者关机了,session也会失效,也可以通过后端程序函数让session失效,invalidate(),该方案是让session中的所有属性失效,常常用于安全退出; 如果需要让session中的某个属性失效,可以使用方法removeAttritue(),如上面的第二个注销函数 session.removeAttribute(“user”); 去掉session中的user属性,注意,一次会话就只有一个session. cookie的生命周期是按累计时间类计算的,不管用户有没有访问过session.

    6.2 单体架构的登录

    6.2.1 单体架构登录理论

    单体架构登录理论 众所周知,HTTP是无状态的协议,这意味着服务器无法确认用户的信息。于是乎,W3C就提出了:给每一个用户都发一个通行证,无论谁访问的时候都需要携带通行证,这样服务器就可以从通行证上确认用户的信息。通行证就是Cookie。 如果说Cookie是检查用户身上的”通行证“来确认用户的身份,那么Session就是通过检查服务器上的”客户明细表“来确认用户的身份的。Session相当于在服务器中建立了一份“客户明细表”。 HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一个用户。于是乎:服务器向用户浏览器发送了一个名为JESSIONID的Cookie,它的值是Session的id值。其实Session是依据Cookie来识别是否是同一个用户。金手指:Session是存放在Cookie里面的,当然也可以存放在URL里面。

    6.2.2 单体架构登录实现思路(三个函数)

    单体架构登录实现思路: 第一,实现登录功能:服务端中,将用户信息保存在Session对象中, 如果在Session对象中能查到,说明已经登录 如果在Session对象中查不到,说明没登录(或者已经退出了登录) 第二,实现注销功能(退出登录):从Session中删除用户的信息 第三,实现记住我功能(关闭掉浏览器后,重新打开浏览器还能保持登录状态):配合Cookie来用(Session默认是)

    金手指:解释三功能(登录、注销、记住我),记住cookie和session重要的点 登录:因为第一次访问,登陆的时候,服务器已经将Cookie发送给浏览器,第二次访问,浏览器就直接带着Cookie到服务器了,所以不用手动登陆了(但是第一次没有存放用户token的Cookie的,必须手动登陆)。 注销:删除服务端内存中的session和浏览器/磁盘中的cookie 记住我:登录后未注销的第 2-n 次登录功能(因为注销会删除内存中的session和浏览器/磁盘中的cookie,所以下一次又要重新发送cookie了)

    6.2.3 Demo:单体架构的登录(三个函数)(重点001)

    总结一下上面代码的思路: 第一个函数:用户登录时,验证用户的账户和密码(第一次登录功能中的 userService.userLogin(mobileNo, password); ) 第一个函数五行代码:生成一个Token保存在数据库中,将用户数据保存在Session中,并将Token写到Cookie中,这个cookie里面有loginToken,这个cookie保存一星期,表示一个星期自动登录(第一个函数五句代码,前三句是写入数据库,后两句是设置session和cookie) 第二个函数,注销,直接删除session和cookie就好(session中去除掉user属性,发命令让浏览器删去磁盘中的cookie文件) 第三个函数,先验证session,然后验证cookie,以下情况: 1、session里面可以取到user属性,直接放行(另:url里面有session,放行); 2、session里面无法取到user属性,看cookie里面有没有loginToken 3、cookie里面有loginToken且合法(该loginToken可以在数据库里面找到对应user),设置好session,放行。 4、cookie里面有loginToken且不合法(该loginToken可以在数据库里面找到对应user),删除这个cookie,拒绝。 5、没有session,没有cookie,转到登录界面

    6.3 多系统登录的问题与解决

    6.3.1 问题1:Session不共享问题(重点002)

    解决系统之间Session不共享问题有一下几种方案: 方案1:Tomcat集群Session全局复制(集群内每个tomcat的session完全同步)【会影响集群的性能呢,不建议】 方案2:根据请求的IP进行Hash映射到对应的机器上(这就相当于请求的IP一直会访问同一个服务器)【如果服务器宕机了,会丢失了一大部分Session的数据,不建议】 方案3:引入一个各个系统都可以访问的中间件Redis,把Session数据放在Redis中,这样所有的系统就都可以知道现在用户登录没有(使用Redis模拟Session)【建议】 共同点(单点登录的核心) 问题:单点登录的问题是session是各个系统所独自拥有的,各个系统不知道用户是否登录,无法共享用户的登录状态, 目标/切入点:目标/切入点是 “一定要让所有的系统就都可以知道现在用户登录没有”,只要能够实现这个目标/切入点,就可以作为方案,所以 Tomcat集群Session全局复制、请求的IP一直会访问同一个服务器、引入中间件Redis 都是方案。

    6.3.2 问题2:Cookie跨域的问题(重点003)

    针对Cookie存在跨域问题,有几种解决方案: 方案1:服务端将Cookie写到客户端后,客户端对Cookie进行解析,将Token解析出来,此后请求都把这个Token带上就行了 方案2:多个域名共享Cookie,具体:服务端在新建Cookie发送给客户端的时候,就设置好Cookie的domain。 方案3:将Token保存在SessionStroage中(不依赖Cookie就没有跨域的问题了) 跨域问题:就是不同域名(一级域名相同二级域名不同)的Cookie,服务端接收不到,但是Cookie不是重点,Cookie里面存放的token才是实现自动登录的重点,token存在于Cookie中,Cookie不能只能让创建cookie的服务端接收到,导致token只能让创建cookie的服务端接收到 切入点/目标:一定要让token在全网服务端都能接收到,知道达到这个目标,都是解决方案 方案1:前端第一个请求服务端,服务端通过Cookie将token给了前端之后,前端将token解析出来,前端 第2-n次请求, 前端就干脆不要再使用Cookie来传输token了,直接将token放到url请求地址后面(笔者上一家公司就是这么做的) 方案2:前端2-n次请求,还是使用Cookie传输token,但是,第一次请求,服务端在新建Cookie发送给客户端的时候,就设置好Cookie的domain,设置为全网Cookie 方案3:将Token保存在SessionStroage中(笔者没用过,电商项目中未登录状态下,添加到购物车可以使用sessionStorage或者LocalStorage做,纯前端Vue操作,都是临时数据存储,sessionStorage表示会话内有效,LocalStorage表示指定时间有效) 小结:URL 顶级Cookie SessionStorage

    6.3.3 Demo:分布式架构的单点登录(三个函数)(重点004)

    单系统登录到分布式系统的单点登录,本质改变: (token,user)是实现自动登录/记住我的关键, 单体系统中, 第一(同一个session中,保存登录状态,不是重点),服务端存在Session里面(session.setAttribute(“user”, user1);)(第一个函数);下次浏览器登录的时候,服务端内存的session存在(页面没有关闭,是同一个浏览器页面)就直接登录了(request.getSession().getAttribute(“user”); )(第三个函数) 第二((token,user)是实现自动登录的重点),服务端将(token,user)存入数据库( user.setLoginToken(loginToken); User user1 = userService.userUpload(user); ),返回给浏览器带有token的Cookie(第一个函数);下次浏览器直接拿着Cookie里面的token来登录,与数据库(token,user)比对(userService.findUserByLoginToken(loginToken); )(第三个函数) 分布式系统中, 第一个,同一个session中,保存登录状态,没有实现了 第二个,((token,user)是实现自动登录的重点),现在将(token,user)存到Redis,返回给浏览器带有token的Cookie(第一个函数 SSO系统 + 业务系统);下次浏览器直接拿着Cookie里面的token来登录,来redis(token,user)比对(第三个函数) 小结:服务端 (token,user) ,之前存放在session,后来在redis中。

    6.4 CAS原理(认证中心服务)

    说到单点登录,就肯定会见到这个名词:CAS (Central Authentication Service),下面说说CAS是怎么搞的。 如果已经将登录单独抽取成系统出来,我们还能这样玩。

    现在我们有两个系统,分别是www.leyou.com和www.leyou2.com,一个SSOwww.sso.com 第一步,用户想要访问系统A www.leyou.com受限的资源(比如说购物车功能,购物车功能需要登录后才能访问),系统A www.leyou.com发现用户并没有登录,于是重定向到sso认证中心,并将自己的地址作为参数。请求的地址如下: www.sso.com?service=www.leyou.com sso认证中心发现用户未登录,将用户引导至登录页面,用户进行输入用户名和密码进行登录,用户与认证中心建立全局会话(生成一份Token,写到Cookie中,保存在浏览器上)

    第二步,认证中心重定向回系统A,并把Token携带过去给系统A,重定向的地址如下: www.leyou.com?token=xxxxxxx 系统A去sso认证中心验证这个Token是否正确,如果正确,则系统A和用户建立局部会话(创建Session)。到此,系统A和用户已经是登录状态了。

    第三步,用户想要访问系统Bwww.leyou2.com受限的资源(比如说订单功能,订单功能需要登录后才能访问),系统Bwww.leyou2.com发现用户并没有登录,于是重定向到sso认证中心,并将自己的地址作为参数。请求的地址如下: www.sso.com?service=www.leyou2.com 注意,因为之前用户与认证中心www.sso.com已经建立了全局会话(当时已经把Cookie保存到浏览器上了),所以这次系统B重定向到认证中心www.sso.com是可以带上Cookie的。 认证中心根据带过来的Cookie发现已经与用户建立了全局会话了,认证中心重定向回系统B,并把Token携带过去给系统B,重定向的地址如下: www.leyou2.com?token=xxxxxxx 接着,系统B去sso认证中心验证这个Token是否正确,如果正确,则系统B和用户建立局部会话(创建Session)。到此,系统B和用户已经是登录状态了。

    所以,其实SSO认证中心就类似一个中转站。

    七、小结

    单点登录SSO 002,完成了。

    天天打码,天天进步!!!

    Processed: 0.016, SQL: 9