cookie实现单点登录
前言
在单点登录方案设计一篇中,我们谈到了目前市面上常用的一些单点登录方案的实现,关于单点登录,只需要把握一个核心的要点即可,那就是:一处登录,处处登录,登录之后,即同域下其他各个系统都能统一拿到用户的基本信息
关于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实现单点登录相关推荐
- SSO单点登录学习总结(2)——基于Cookie+fliter单点登录实例
1.使用Cookie解决单点登录 技术点: 1.设置Cookie的路径为setPath("/").即Tomcat的目录下都有效 2.设置Cookie的域setDomain(&quo ...
- 【No.1】基于Cookie的单点登录(SSO)
2019独角兽企业重金招聘Python工程师标准>>> 这篇主要说明基于Cookie的单点登录实现,以及Cookie的一些特性以及使用说明. 1.Cookie是什么,如何工作的 在程 ...
- 基于Cookie的单点登录(SSO)系统介绍
基于Cookie的单点登录(SSO)系统介绍 SSO的概念: 单点登录SSO(Single Sign-On)是身份管理中的一部分.SSO的一种较为通俗的定义是:SSO是指访问同一服务器不同应用中的受保 ...
- 跨域 Cookie 实现单点登录
单点登录 单点登录(SSO - Single Sign On):对于同一个客户端(例如 Chrome 浏览器),只要登录了一个子站(例如 a.com),则所有子站(b.com.c.com)都认为已经登 ...
- SSO单点登录-基于cookie的单点登录
1.概述 单点登录(Single-Sign-On),简称SSO,它的解释为:在多个应用系统中,只要登陆一次,便可以访问其它相互信任的系统.早期系统由于只有一个服务,因此只需要登录一次,就可以访问系统的 ...
- 基于cookie的SSO单点登录系统
利用COOKIE实现单点登录功能 近期公司要求帮一个项目实现单点登录功能,在综合考量下决定采用cookie实现,大概的流程如下图所: 转载于:https://www.cnblogs.com/bugge ...
- cookie跨域,实现单点登录
Cookie 跨域,实现单点登录 Table title 最近在做一个单点登录的系统整合项目,之前我们使用控件实现单点登录(以后可以介绍一下).但现 ...
- Cookie同域,跨域单点登录
Cookie 同域单点登录最近在做一个单点登录的系统整合项目,之前我们使用控件实现单点登录(以后可以介绍一下).但现在为了满足客户需求,在不使用控件情况下实现单点登录,先来介绍一下单点登录.单点登录: ...
- cas单点登录原理碎碎念
2019独角兽企业重金招聘Python工程师标准>>> 也许有一天,你去面试,当面试官问你做cas的经验的时候,你会怎么描述? cas单点登录的原理? 单点登录是分为基于sessio ...
最新文章
- PortableApps的使用方法
- 【js】将json类型的数组或对象转为字符串
- android学习第5天(周六日没学,可惜啊,神驰物外了)
- Python用selenium获取cookie以后给rqeuests使用。
- 【招聘(广州)】-年薪30W起-自助打印领域业内第一
- win10下Redis集群搭建的详细步骤
- life of a NPTL pthread
- MoSE: 多任务混合序列专家模型
- Backup Volume 操作 - 每天5分钟玩转 OpenStack(59)
- mysql慢日志分析工具_mysql慢查日志分析工具 percona-toolkit
- UNIX网络编程——常用服务器模型总结
- Unity音频可视化插件
- Ps调色磨皮降噪抠图胶片特效模拟常用100款滤镜合集一键安装支持PSCC2015-2019win64
- html移动图片广告代码,右下角弹出广告代码 控制div移动 1)div是否
- Android xUtils框架最全使用详解
- Telink blt_soft_timer 改进
- SVN update拒绝访问,clean up失败
- 在Vue中使用svg格式字体图标
- druid1.2.8源码悦读:第五天
- Java高级工程师面试题目汇集