前言

在单点登录方案设计一篇中,我们谈到了目前市面上常用的一些单点登录方案的实现,关于单点登录,只需要把握一个核心的要点即可,那就是:一处登录,处处登录,登录之后,即同域下其他各个系统都能统一拿到用户的基本信息

关于cookie,想必大家也很熟悉了,cookie中可以存储会话信息,将用户的基本信息存储进去之后,就可以在前后端交互中进行传输了

本篇将分享基于cookie如何实现单点登录,本篇以实际案例为主进行演示

业务背景

以一个大家熟悉的购物业务,实际项目中,一个商城系统可能包含诸多模块的业务,比如用户中心,专门负责用户的认证功能,购物车、会员、物流等多个板块,用户只需要一次登录之后,就可以在各个业务模块的界面中来回切换

环境准备

JDK8 , maven , idea

实现目标

  • 用户通过用户模块的主页面登录之后,可以访问任意的其他模块,比如购物车模块的页面
  • 未登录访问其他任何模块,直接跳转到登录页面
  • 用户模块退出之后,再访问其他任何模块的页面,将无法访问

工程搭建

整个工程结构如上图所示, 各个模块的说明如下:

  • sso-main 业务主模块,也是登录的入口
  • sso-login 负责登录业务,实际可理解为用户中心
  • sso-cart 购物车模块
  • sso-vip 会员模块

后续可以在此基础上继续增加,比如积分模块,物流模块等

1、顶级pom依赖

 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.2.1.RELEASE</version></dependency></dependencies></dependencyManagement>

sso-login模块

该模块主要负责用户的登录,退出,并将用户会话信息存储至token,同时为其他各个模块提供用户信息查询功能

1、pom依赖

     <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency></dependencies>

2、yml配置

server:port: 9000

3、登录页面

提供一个基于thymeleaf的html登录模板页

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head><meta charset="UTF-8"><title>login</title>
</head>
<body>
<h3>登陆页面</h3>
<form action="/login" method="post">用户名:<input type="text" name="username"/>密码:<input type="password" name="password"/><input type="submit" value="登录"/><p style="color: red" th:text="${session.msg}"></p>
</form>
</body>
</html>

4、提供一个controller

ViewController 用户进行跳转至登录页,作为引导控制器

/*** 页面跳转逻辑*/
@Controller
@RequestMapping("/view")
public class ViewController {/*** 跳转到登陆页面,设置重定向的地址,可以携带cookie-TOKEN,如果有cookie,不用跳转到登陆页面,直接重定向* @return*/@GetMapping("/login")public String toLogin(@RequestParam(required = false, defaultValue = "") String target,HttpSession session, @CookieValue(required = false, value = "TOKEN") Cookie cookie){//传入参数为空,默认跳转到首页if (StringUtils.isEmpty(target)){target = "http://127.0.0.1:9010";}//若已经登陆的用户登陆系统时,直接重定向到targetif (cookie != null){String value = cookie.getValue();User user = LoginCacheUtil.loginMap.get(value);if (user != null){return "redirect:" + target;}}//重定向地址,将地址保存起来session.setAttribute("target",target);return "login";}
}

LoginController 实际处理登录逻辑的控制器,提供登录,登出,以及查询用户信息的接口,这里为了模拟数据库的用户,就直接在程序中模拟初始化一些用户数据

@Controller
@RequestMapping("/login")
public class LoginController {private static Set<User> dbUsers;/*** 模拟数据库的用户列表*/static {dbUsers = new HashSet<>();dbUsers.add(new User(0,"zhangsan","123456"));dbUsers.add(new User(1,"lisi","123456"));dbUsers.add(new User(2,"wangwu","123456"));}@PostMappingpublic String doLogin(User user, HttpSession session, HttpServletResponse response){String target = (String) session.getAttribute("target");User res = null;for (User dbUser : dbUsers){if(dbUser.getUsername().equals(user.getUsername()) && dbUser.getPassword().equals(user.getPassword())){res = dbUser;}}//用户登陆成功,保存(TOKEN,用户)if (res != null){//保存用户登陆信息String token = UUID.randomUUID().toString();Cookie cookie = new Cookie("TOKEN",token);cookie.setDomain("127.0.0.1");response.addCookie(cookie);LoginCacheUtil.loginMap.put(token,user);} else {session.setAttribute("msg","用户名或密码错误");return "login";}//重定向到target地址return "redirect:" + target;}/*** 给其他子系统开发一个接口,根据token获取登陆的用户信息* @param token* @return*/@GetMapping("/info")public ResponseEntity<User> getUserInfo(String token){if (!StringUtils.isEmpty(token)){User user = LoginCacheUtil.loginMap.get(token);return ResponseEntity.ok(user);}else {return new ResponseEntity<>(HttpStatus.BAD_REQUEST);}}@GetMapping("/logout")public String logout(@CookieValue(value = "TOKEN")Cookie cookie, @RequestParam("target") String target,HttpSession session,HttpServletResponse response){//删除用户的登陆信息LoginCacheUtil.loginMap.remove(cookie.getValue());//删除session.loginUsersession.removeAttribute("loginUser");//设置cookie过期Cookie newCookie = new Cookie("TOKEN",null);newCookie.setMaxAge(0);newCookie.setPath("/");response.addCookie(newCookie);return "redirect:"+target;}}

sso-main 模块

1、pom依赖

同 sso-login

2、yml配置

server:port: 9010

3、配置简单的html

由于需要进行页面展示,并模拟登录过程,这里使用了thymeleaf模板,使用比较简单,main模块的页面主要负责登录之后跳转后的主页面,简单起见,只需要对登录之后的用户信息做一下展示即可

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head><meta charset="UTF-8"><title>首页</title>
</head>
<body><h1>欢迎来到main首页</h1><span><a th:if="${session.loginUser} == null" href="http://127.0.0.1:9000/view/login?target=http://127.0.0.1:9010/view/index">登陆</a><a th:unless="${session.loginUser} == null" href="http://127.0.0.1:9000/login/logout?target=http://127.0.0.1:9010/view/index">退出</a></span><p th:unless="${session.loginUser} == null"><span style="color: deepskyblue" th:text="${session.loginUser.username}"></span>已登陆</p>
</body>
</html>

4、编写一个controller

当用户登录成功后,需要进行页面跳转,即跳转到main的页面,由于是不同的业务模块,实际项目中,可能是分布式部署的话,为了拿到用户的登录信息,这里简单采用restTemplate的方式

@Controller
@RequestMapping("/view")
public class ViewController {@Autowiredprivate RestTemplate restTemplate;private final static String REMOTE_LOGIN_INFO_ADDRESS = "http://127.0.0.1:9000/login/info?token=";@GetMapping("/index")public String toIndex(@CookieValue(required = false, value = "TOKEN")Cookie cookie, HttpSession session){if (cookie != null){String token = cookie.getValue();//根据login子系统暴露的info方法去根据token获取userif (!StringUtils.isEmpty(token)){Map result = restTemplate.getForObject(REMOTE_LOGIN_INFO_ADDRESS + token, Map.class);session.setAttribute("loginUser",result);}}return "index";}
}

sso - main 和sso-login模块写好之后,我们就可以简单做个测试了,分别启动这两个模块

1、访问登录入口页面,http://127.0.0.1:9010/view/index

2、点击登录

登录之后,由sso-main模块中的页面上的a链接,携带一个完整的href地址跳转到sso-login的登录主页面,我们的实现逻辑是,进入登录页面时,由于携带了完整的url信息,里面包含了从哪个页面(这里从main模块)过来的,那么在sso-login登录成功之后,还能跳转回去原来的页面

3、输入程序中初始化的用户名和密码


在sso-login的doLogin接口处理完成之后,将会把登录用户的信息写入session并带来页面上进行展示;

同时用户登录成功后,在接口中我们生成了一个token,用一个map进行存储,token作为key,user对象作为value,方便在后续其他模块访问的时候,直接从map中快速获取,关于这一点,在实际开发项目中,可以考虑使用threadLocal进行存储

如何验证我们的单点登录是好使的呢?还没有完,上面只是完成了用户的登录,接下来我们再将另外2个模块的业务逻辑编写完整

sso-vip 模块

该模块作为会员业务模块,另外的cart购物车模块也是如此

1、pom依赖

如上的sso-login

2、yml配置

server:port: 9011

3、html页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head><meta charset="UTF-8"><title>Vip</title>
</head>
<body><h1>欢迎来到VIP页面</h1><span><a th:if="${session.loginUser} == null" href="http://127.0.0.1:9000/view/login?target=http://127.0.0.1:9011/view/index">登陆</a><a th:unless="${session.loginUser} == null" href="http://127.0.0.1:9000/login/logout?target=http://127.0.0.1:9011/view/index">退出</a></span><p th:unless="${session.loginUser} == null"><span style="color: deepskyblue" th:text="${session.loginUser.username}"></span>已登陆</p>
</body>
</html>

该页面主要展示登录成功后的用户信息

4、提供一个controller,用于从sso-login中获取用户信息

@Controller
@RequestMapping("/view")
public class ViewController {@Autowiredprivate RestTemplate restTemplate;private final static String REMOTE_LOGIN_INFO_ADDRESS = "http://127.0.0.1:9000/login/info?token=";@GetMapping("/index")public String toIndex(@CookieValue(required = false, value = "TOKEN")Cookie cookie, HttpSession session){if (cookie != null){String token = cookie.getValue();if (!StringUtils.isEmpty(token)){Map result = restTemplate.getForObject(REMOTE_LOGIN_INFO_ADDRESS + token, Map.class);session.setAttribute("loginUser",result);}}return "index";}
}

接下来启动该模块,下面开始测试,

1、在已经登录的情况下,输入:http://localhost:9011/view/index

已经登录的情况下,VIP模块的controller中通过rest接口可以拿到cookie中用户的信息,因此直接将用户信息取出进行页面展示

2、在未登录的情况下,输入:http://localhost:9011/view/index

先点击退出登录,然后直接展示登录页面的信息

最后我们将cart模块也仿照vip博客搭建好,启动项目,然后再次做测试,

3、访问car模块主页面:http://127.0.0.1:9012/view/index

由于第二步中进行了退出,这时候,访问cart页面时,也是需要登录的状态

4、cart中点击登录,并进行登录操作

登录成功后,我们再次刷新cart页面,这时候显示已登录

通过以上案例的展示,我们基于cookie的方式实现了模拟单点登录的效果,此种方案,在不少生产级的项目中仍有使用,关于cookie实现单点登录,做一下简单的说明

优点:

  • 实现难度低,无需太多的学习成本
  • 后续维护相对简单
  • 新的业务模块加入时,扩展实现成本较低

缺点:

  • cookie中如果存放了用户的敏感信息,一旦被窃取,这个负面影响大,生产环境,最好使用加密手段加密
  • 如果大量的用户信息放入cookie,对浏览器端压力较大,同时cookie存储的信息量大小有限
  • 不够轻量级,跨域情况下将会带来一定的麻烦(这种问题,需要考虑到自身系统和外部系统对接问题)

cookie实现单点登录相关推荐

  1. SSO单点登录学习总结(2)——基于Cookie+fliter单点登录实例

    1.使用Cookie解决单点登录 技术点: 1.设置Cookie的路径为setPath("/").即Tomcat的目录下都有效 2.设置Cookie的域setDomain(&quo ...

  2. 【No.1】基于Cookie的单点登录(SSO)

    2019独角兽企业重金招聘Python工程师标准>>> 这篇主要说明基于Cookie的单点登录实现,以及Cookie的一些特性以及使用说明. 1.Cookie是什么,如何工作的 在程 ...

  3. 基于Cookie的单点登录(SSO)系统介绍

    基于Cookie的单点登录(SSO)系统介绍 SSO的概念: 单点登录SSO(Single Sign-On)是身份管理中的一部分.SSO的一种较为通俗的定义是:SSO是指访问同一服务器不同应用中的受保 ...

  4. 跨域 Cookie 实现单点登录

    单点登录 单点登录(SSO - Single Sign On):对于同一个客户端(例如 Chrome 浏览器),只要登录了一个子站(例如 a.com),则所有子站(b.com.c.com)都认为已经登 ...

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

    1.概述 单点登录(Single-Sign-On),简称SSO,它的解释为:在多个应用系统中,只要登陆一次,便可以访问其它相互信任的系统.早期系统由于只有一个服务,因此只需要登录一次,就可以访问系统的 ...

  6. 基于cookie的SSO单点登录系统

    利用COOKIE实现单点登录功能 近期公司要求帮一个项目实现单点登录功能,在综合考量下决定采用cookie实现,大概的流程如下图所: 转载于:https://www.cnblogs.com/bugge ...

  7. cookie跨域,实现单点登录

      Cookie 跨域,实现单点登录 Table title                         最近在做一个单点登录的系统整合项目,之前我们使用控件实现单点登录(以后可以介绍一下).但现 ...

  8. Cookie同域,跨域单点登录

    Cookie 同域单点登录最近在做一个单点登录的系统整合项目,之前我们使用控件实现单点登录(以后可以介绍一下).但现在为了满足客户需求,在不使用控件情况下实现单点登录,先来介绍一下单点登录.单点登录: ...

  9. cas单点登录原理碎碎念

    2019独角兽企业重金招聘Python工程师标准>>> 也许有一天,你去面试,当面试官问你做cas的经验的时候,你会怎么描述? cas单点登录的原理? 单点登录是分为基于sessio ...

最新文章

  1. PortableApps的使用方法
  2. 【js】将json类型的数组或对象转为字符串
  3. android学习第5天(周六日没学,可惜啊,神驰物外了)
  4. Python用selenium获取cookie以后给rqeuests使用。
  5. 【招聘(广州)】-年薪30W起-自助打印领域业内第一
  6. win10下Redis集群搭建的详细步骤
  7. life of a NPTL pthread
  8. MoSE: 多任务混合序列专家模型
  9. Backup Volume 操作 - 每天5分钟玩转 OpenStack(59)
  10. mysql慢日志分析工具_mysql慢查日志分析工具 percona-toolkit
  11. UNIX网络编程——常用服务器模型总结
  12. Unity音频可视化插件
  13. Ps调色磨皮降噪抠图胶片特效模拟常用100款滤镜合集一键安装支持PSCC2015-2019win64
  14. html移动图片广告代码,右下角弹出广告代码 控制div移动 1)div是否
  15. Android xUtils框架最全使用详解
  16. Telink blt_soft_timer 改进
  17. SVN update拒绝访问,clean up失败
  18. 在Vue中使用svg格式字体图标
  19. druid1.2.8源码悦读:第五天
  20. Java高级工程师面试题目汇集

热门文章

  1. 汇编(二)——微机原理与接口
  2. SRAM FPGA控制,实际产品拷机测试过
  3. 隐藏服务器header与web软件版本信息
  4. 【供应链】全面分析供应链类型
  5. 「科普」一文读懂生产制造MES系统
  6. 关于奋斗的150条中国名人名言
  7. 软件开发团队成员分工_分析软件开发人员的能力–选择合适的团队成员
  8. 电脑硬件升级——笔记本更换更大容量的固态硬盘,并进行系统迁移
  9. ps导出的gif图片不能动,或是只能动一次的解决办法
  10. 华为路ws5200设置虚拟服务器,华为路由器WS5200如何设置上网 最详细的华为路由器WS5200上网设置方法教程...